import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js'; import { SqliteNativeQueryBuilder } from './SqliteNativeQueryBuilder.js'; import { SqliteSchemaHelper } from './SqliteSchemaHelper.js'; import { SqliteExceptionConverter } from './SqliteExceptionConverter.js'; export class SqlitePlatform extends AbstractSqlPlatform { schemaHelper = new SqliteSchemaHelper(this); exceptionConverter = new SqliteExceptionConverter(); /** @internal */ createNativeQueryBuilder() { return new SqliteNativeQueryBuilder(this); } usesDefaultKeyword() { return false; } usesReturningStatement() { return true; } usesEnumCheckConstraints() { return true; } getCurrentTimestampSQL(length) { return `(strftime('%s', 'now') * 1000)`; } getDateTimeTypeDeclarationSQL(column) { return 'datetime'; } getBeginTransactionSQL(options) { return ['begin']; } getEnumTypeDeclarationSQL(column) { if (column.items?.every(item => typeof item === 'string')) { return 'text'; } /* v8 ignore next */ return this.getTinyIntTypeDeclarationSQL(column); } getTinyIntTypeDeclarationSQL(column) { return this.getIntegerTypeDeclarationSQL(column); } getSmallIntTypeDeclarationSQL(column) { return this.getIntegerTypeDeclarationSQL(column); } getIntegerTypeDeclarationSQL(column) { return 'integer'; } getFloatDeclarationSQL() { return 'real'; } getBooleanTypeDeclarationSQL() { return 'integer'; } getCharTypeDeclarationSQL(column) { return 'text'; } getVarcharTypeDeclarationSQL(column) { return 'text'; } normalizeColumnType(type, options) { const simpleType = this.extractSimpleType(type); if (['varchar', 'text'].includes(simpleType)) { return this.getVarcharTypeDeclarationSQL(options); } return simpleType; } convertsJsonAutomatically() { return false; } /** * This is used to narrow the value of Date properties as they will be stored as timestamps in sqlite. * We use this method to convert Dates to timestamps when computing the changeset, so we have the right * data type in the payload as well as in original entity data. Without that, we would end up with diffs * including all Date properties, as we would be comparing Date object with timestamp. */ processDateProperty(value) { if (value instanceof Date) { return +value; } return value; } getIndexName(tableName, columns, type) { if (type === 'primary') { return this.getDefaultPrimaryName(tableName, columns); } return super.getIndexName(tableName, columns, type); } supportsDeferredUniqueConstraints() { return false; } /** * SQLite supports schemas via ATTACH DATABASE. Returns true when there are * attached databases configured. */ supportsSchemas() { const attachDatabases = this.config.get('attachDatabases'); return !!attachDatabases?.length; } getDefaultSchemaName() { // Return 'main' only when schema support is active (i.e., databases are attached) return this.supportsSchemas() ? 'main' : undefined; } getFullTextWhereClause() { return `:column: match :query`; } escape(value) { if (value == null) { return 'null'; } if (typeof value === 'boolean') { return value ? 'true' : 'false'; } if (typeof value === 'number' || typeof value === 'bigint') { return '' + value; } if (value instanceof Date) { return '' + +value; } if (Array.isArray(value)) { return value.map(v => this.escape(v)).join(', '); } if (Buffer.isBuffer(value)) { return `X'${value.toString('hex')}'`; } return `'${String(value).replace(/'/g, "''")}'`; } convertVersionValue(value, prop) { if (prop.runtimeType === 'Date') { const ts = +value; const str = new Date(ts) .toISOString() .replace('T', ' ') .replace(/\.\d{3}Z$/, ''); return { $in: [ts, str] }; } return value; } getJsonArrayElementPropertySQL(alias, property, _type) { return `json_extract(${this.quoteIdentifier(alias)}.value, '$.${this.quoteJsonKey(property)}')`; } quoteValue(value) { if (value instanceof Date) { return '' + +value; } return super.quoteValue(value); } }