diff --git a/pom.xml b/pom.xml
index 68110a6b..c3f02707 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
qqq-backend-module-api
qqq-backend-module-filesystem
qqq-backend-module-rdbms
+ qqq-backend-module-sqlite
qqq-backend-module-mongodb
qqq-language-support-javascript
qqq-openapi
diff --git a/qqq-backend-module-sqlite/pom.xml b/qqq-backend-module-sqlite/pom.xml
new file mode 100644
index 00000000..106dede0
--- /dev/null
+++ b/qqq-backend-module-sqlite/pom.xml
@@ -0,0 +1,113 @@
+
+
+
+
+ 4.0.0
+
+ qqq-backend-module-sqlite
+
+
+ com.kingsrook.qqq
+ qqq-parent-project
+ ${revision}
+
+
+
+
+
+
+
+
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-rdbms
+ ${revision}
+
+
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.47.1.0
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4.3
+
+ false
+
+
+ *:*
+
+ META-INF/*
+
+
+
+
+
+
+ ${plugin.shade.phase}
+
+ shade
+
+
+
+
+
+
+
+
diff --git a/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/SQLiteBackendModule.java b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/SQLiteBackendModule.java
new file mode 100644
index 00000000..b733f796
--- /dev/null
+++ b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/SQLiteBackendModule.java
@@ -0,0 +1,57 @@
+package com.kingsrook.qqq.backend.module.sqlite;
+
+
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
+import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
+import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule;
+import com.kingsrook.qqq.backend.module.sqlite.model.metadata.SQLiteBackendMetaData;
+import com.kingsrook.qqq.backend.module.sqlite.model.metadata.SQLiteTableBackendDetails;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SQLiteBackendModule extends RDBMSBackendModule
+{
+ private static final QLogger LOG = QLogger.getLogger(SQLiteBackendModule.class);
+
+ private static final String NAME = "sqlite";
+
+ static
+ {
+ QBackendModuleDispatcher.registerBackendModule(new SQLiteBackendModule());
+ }
+
+ /*******************************************************************************
+ ** Method where a backend module must be able to provide its type (name).
+ *******************************************************************************/
+ public String getBackendType()
+ {
+ return NAME;
+ }
+
+
+
+ /*******************************************************************************
+ ** Method to identify the class used for backend meta data for this module.
+ *******************************************************************************/
+ @Override
+ public Class extends QBackendMetaData> getBackendMetaDataClass()
+ {
+ return (SQLiteBackendMetaData.class);
+ }
+
+
+
+ /*******************************************************************************
+ ** Method to identify the class used for table-backend details for this module.
+ *******************************************************************************/
+ @Override
+ public Class extends QTableBackendDetails> getTableBackendDetailsClass()
+ {
+ return (SQLiteTableBackendDetails.class);
+ }
+
+}
diff --git a/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/model/metadata/SQLiteBackendMetaData.java b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/model/metadata/SQLiteBackendMetaData.java
new file mode 100644
index 00000000..25f1af26
--- /dev/null
+++ b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/model/metadata/SQLiteBackendMetaData.java
@@ -0,0 +1,119 @@
+package com.kingsrook.qqq.backend.module.sqlite.model.metadata;
+
+
+import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
+import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
+import com.kingsrook.qqq.backend.module.rdbms.strategy.RDBMSActionStrategyInterface;
+import com.kingsrook.qqq.backend.module.sqlite.SQLiteBackendModule;
+import com.kingsrook.qqq.backend.module.sqlite.strategy.SQLiteRDBMSActionStrategy;
+import org.sqlite.JDBC;
+
+
+/*******************************************************************************
+ ** Meta-data to provide details of an SQLite backend (e.g., path to the database file)
+ *******************************************************************************/
+public class SQLiteBackendMetaData extends RDBMSBackendMetaData
+{
+ private String path;
+
+ // todo - overrides to setters for unsupported fields?
+ // todo - or - change rdbms connection manager to not require an RDBMSBackendMetaData?
+
+
+
+ /*******************************************************************************
+ ** Default Constructor.
+ *******************************************************************************/
+ public SQLiteBackendMetaData()
+ {
+ super();
+ setVendor("sqlite");
+ setBackendType(SQLiteBackendModule.class);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String buildConnectionString()
+ {
+ return "jdbc:sqlite:" + this.path;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String getJdbcDriverClassName()
+ {
+ return (JDBC.class.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public SQLiteBackendMetaData withName(String name)
+ {
+ setName(name);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for path
+ *******************************************************************************/
+ public String getPath()
+ {
+ return (this.path);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for path
+ *******************************************************************************/
+ public void setPath(String path)
+ {
+ this.path = path;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for path
+ *******************************************************************************/
+ public SQLiteBackendMetaData withPath(String path)
+ {
+ this.path = path;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public RDBMSActionStrategyInterface getActionStrategy()
+ {
+ if(getActionStrategyField() == null)
+ {
+ if(getActionStrategyCodeReference() != null)
+ {
+ setActionStrategyField(QCodeLoader.getAdHoc(RDBMSActionStrategyInterface.class, getActionStrategyCodeReference()));
+ }
+ else
+ {
+ setActionStrategyField(new SQLiteRDBMSActionStrategy());
+ }
+ }
+
+ return (getActionStrategyField());
+ }
+}
diff --git a/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/model/metadata/SQLiteTableBackendDetails.java b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/model/metadata/SQLiteTableBackendDetails.java
new file mode 100644
index 00000000..98430d91
--- /dev/null
+++ b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/model/metadata/SQLiteTableBackendDetails.java
@@ -0,0 +1,45 @@
+package com.kingsrook.qqq.backend.module.sqlite.model.metadata;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SQLiteTableBackendDetails extends QTableBackendDetails
+{
+ private String tableName;
+
+
+
+ /*******************************************************************************
+ ** Getter for tableName
+ *******************************************************************************/
+ public String getTableName()
+ {
+ return (this.tableName);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for tableName
+ *******************************************************************************/
+ public void setTableName(String tableName)
+ {
+ this.tableName = tableName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for tableName
+ *******************************************************************************/
+ public SQLiteTableBackendDetails withTableName(String tableName)
+ {
+ this.tableName = tableName;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/strategy/SQLiteRDBMSActionStrategy.java b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/strategy/SQLiteRDBMSActionStrategy.java
new file mode 100644
index 00000000..ee948e1d
--- /dev/null
+++ b/qqq-backend-module-sqlite/src/main/java/com/kingsrook/qqq/backend/module/sqlite/strategy/SQLiteRDBMSActionStrategy.java
@@ -0,0 +1,133 @@
+package com.kingsrook.qqq.backend.module.sqlite.strategy;
+
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.utils.ValueUtils;
+import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
+import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
+
+
+/*******************************************************************************
+ ** SQLite specialization of the default RDBMS/JDBC action strategy
+ *******************************************************************************/
+public class SQLiteRDBMSActionStrategy extends BaseRDBMSActionStrategy
+{
+
+ /***************************************************************************
+ ** deal with sqlite not having temporal types... so temporal values
+ ** i guess are stored as strings, as that's how they come back to us - so
+ ** the JDBC methods fail trying to getDate or whatever from them - but
+ ** getting the values as strings, they parse nicely, so do that.
+ ***************************************************************************/
+ @Override
+ public Serializable getFieldValueFromResultSet(QFieldType type, ResultSet resultSet, int i) throws SQLException
+ {
+ return switch(type)
+ {
+ case DATE ->
+ {
+ try
+ {
+ yield parseString(s -> LocalDate.parse(s), resultSet, i);
+ }
+ catch(Exception e)
+ {
+ /////////////////////////////////////////////////////////////////////////////////
+ // handle the case of, the value we got back is actually a date-time -- so -- //
+ // let's parse it as such, and then map into a LocalDate in the session zoneId //
+ /////////////////////////////////////////////////////////////////////////////////
+ Instant instant = (Instant) parseString(s -> Instant.parse(s), resultSet, i);
+ if(instant == null)
+ {
+ yield null;
+ }
+ ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
+ yield instant.atZone(zoneId).toLocalDate();
+ }
+ }
+ case TIME -> parseString(s -> LocalTime.parse(s), resultSet, i);
+ case DATE_TIME -> parseString(s -> Instant.parse(s), resultSet, i);
+ default -> super.getFieldValueFromResultSet(type, resultSet, i);
+ };
+ }
+
+
+
+ /***************************************************************************
+ ** helper method for getFieldValueFromResultSet
+ ***************************************************************************/
+ private Serializable parseString(Function parser, ResultSet resultSet, int i) throws SQLException
+ {
+ String valueString = QueryManager.getString(resultSet, i);
+ if(valueString == null)
+ {
+ return (null);
+ }
+ else
+ {
+ return parser.apply(valueString);
+ }
+ }
+
+
+
+ /***************************************************************************
+ * bind temporal types as strings (see above comment re: sqlite temporal types)
+ ***************************************************************************/
+ @Override
+ protected int bindParamObject(PreparedStatement statement, int index, Object value) throws SQLException
+ {
+ if(value instanceof Instant || value instanceof LocalTime || value instanceof LocalDate)
+ {
+ bindParam(statement, index, value.toString());
+ return 1;
+ }
+ else
+ {
+ return super.bindParamObject(statement, index, value);
+ }
+ }
+
+
+
+ /***************************************************************************
+ ** per discussion (and rejected PR mentioned) on https://github.com/prrvchr/sqlite-jdbc
+ ** sqlite jdbc by default will only return the latest generated serial. but we can get
+ ** them all by appending this "RETURNING id" to the query, and then calling execute()
+ ** (instead of executeUpdate()) and getResultSet (instead of getGeneratedKeys())
+ ***************************************************************************/
+ @Override
+ public List executeInsertForGeneratedIds(Connection connection, String sql, List