ts-prorm-orm 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1111 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/audit/history-query.js +180 -0
- package/dist/audit/index.js +23 -0
- package/dist/audit/logger.js +236 -0
- package/dist/cache/cache-manager.js +84 -0
- package/dist/cache/index.js +16 -0
- package/dist/cache/redis-cluster-cache.js +557 -0
- package/dist/cli.js +2200 -0
- package/dist/compliance/audit-trail.js +68 -0
- package/dist/compliance/backup-verification.js +685 -0
- package/dist/compliance/breach-detector.js +505 -0
- package/dist/compliance/consent-record.js +227 -0
- package/dist/compliance/consent-versioning.js +331 -0
- package/dist/compliance/cross-border-log.js +530 -0
- package/dist/compliance/data-classifier.js +258 -0
- package/dist/compliance/data-lineage.js +565 -0
- package/dist/compliance/data-masker.js +303 -0
- package/dist/compliance/data-portability.js +265 -0
- package/dist/compliance/data-retention.js +195 -0
- package/dist/compliance/dsar-workflow.js +342 -0
- package/dist/compliance/field-encryption.js +223 -0
- package/dist/compliance/immutable-record.js +148 -0
- package/dist/compliance/index.js +260 -0
- package/dist/compliance/privacy-impact-assessment.js +364 -0
- package/dist/compliance/pseudonymization.js +269 -0
- package/dist/compliance/query-firewall.js +1182 -0
- package/dist/compliance/rate-limiter.js +580 -0
- package/dist/compliance/right-to-erasure.js +117 -0
- package/dist/compliance/row-level-security.js +246 -0
- package/dist/compliance/security-decorator.js +574 -0
- package/dist/compliance/security-monitor.js +525 -0
- package/dist/compliance/sensitive-data-discovery.js +476 -0
- package/dist/compliance/session-isolation.js +472 -0
- package/dist/compliance/tls-enforcer.js +108 -0
- package/dist/compliance/worm-storage.js +712 -0
- package/dist/connection-manager.js +198 -0
- package/dist/connection-pool.js +519 -0
- package/dist/decorators/audit.js +136 -0
- package/dist/decorators/belongs-to-many.js +115 -0
- package/dist/decorators/belongs-to.js +115 -0
- package/dist/decorators/check.js +435 -0
- package/dist/decorators/collate.js +329 -0
- package/dist/decorators/comment.js +205 -0
- package/dist/decorators/database-settings.js +236 -0
- package/dist/decorators/default.js +244 -0
- package/dist/decorators/encryption.js +235 -0
- package/dist/decorators/engine.js +97 -0
- package/dist/decorators/fk-constraints.js +594 -0
- package/dist/decorators/foreign-table.js +136 -0
- package/dist/decorators/generated.js +274 -0
- package/dist/decorators/has-many.js +127 -0
- package/dist/decorators/has-one.js +116 -0
- package/dist/decorators/hstore.js +129 -0
- package/dist/decorators/index.js +355 -0
- package/dist/decorators/json-column.js +83 -0
- package/dist/decorators/jsonb.js +101 -0
- package/dist/decorators/orm-decorators.js +425 -0
- package/dist/decorators/permissions.js +294 -0
- package/dist/decorators/procedure.js +210 -0
- package/dist/decorators/query-options.js +556 -0
- package/dist/decorators/range.js +167 -0
- package/dist/decorators/set-column.js +83 -0
- package/dist/decorators/spatial.js +114 -0
- package/dist/decorators/storage.js +580 -0
- package/dist/decorators/timezone.js +512 -0
- package/dist/decorators/trigger.js +90 -0
- package/dist/decorators/uuid.js +135 -0
- package/dist/decorators/view.js +258 -0
- package/dist/diagrams/chen-diagram.js +354 -0
- package/dist/diagrams/class-diagram.js +384 -0
- package/dist/diagrams/dependency-diagram.js +432 -0
- package/dist/diagrams/er-diagram.js +605 -0
- package/dist/diagrams/flow-diagram.js +394 -0
- package/dist/diagrams/gantt-diagram.js +411 -0
- package/dist/diagrams/index-diagram.js +353 -0
- package/dist/diagrams/index.js +184 -0
- package/dist/diagrams/migration-diagram.js +316 -0
- package/dist/diagrams/model-diagram.js +616 -0
- package/dist/diagrams/package-diagram.js +376 -0
- package/dist/diagrams/palette.js +85 -0
- package/dist/diagrams/relational-diagram.js +455 -0
- package/dist/diagrams/schemadoc-diagram.js +309 -0
- package/dist/diagrams/sequence-diagram.js +307 -0
- package/dist/diagrams/state-diagram.js +344 -0
- package/dist/diagrams/svg-dom.js +111 -0
- package/dist/diagrams/tree-diagram.js +250 -0
- package/dist/dialects/clickhouse/index.js +1742 -0
- package/dist/dialects/cockroachdb/index.js +4677 -0
- package/dist/dialects/cratedb/index.js +441 -0
- package/dist/dialects/databricks/index.js +517 -0
- package/dist/dialects/db2/index.js +2225 -0
- package/dist/dialects/dialect.js +720 -0
- package/dist/dialects/duckdb/index.js +2014 -0
- package/dist/dialects/exasol/index.js +357 -0
- package/dist/dialects/firebird/index.js +495 -0
- package/dist/dialects/greenplum/index.js +457 -0
- package/dist/dialects/hana/index.js +1741 -0
- package/dist/dialects/mariadb/index.js +3590 -0
- package/dist/dialects/mssql/index.js +2296 -0
- package/dist/dialects/mysql/index.js +4058 -0
- package/dist/dialects/oracle/index.js +2999 -0
- package/dist/dialects/postgres/index.js +5319 -0
- package/dist/dialects/query-stream-helper.js +152 -0
- package/dist/dialects/questdb/index.js +433 -0
- package/dist/dialects/redshift/index.js +2277 -0
- package/dist/dialects/singlestore/index.js +354 -0
- package/dist/dialects/snowflake/index.js +2082 -0
- package/dist/dialects/spanner/index.js +1768 -0
- package/dist/dialects/sqlite/index.js +3382 -0
- package/dist/dialects/tidb/index.js +377 -0
- package/dist/dialects/timescaledb/index.js +164 -0
- package/dist/dialects/trino/index.js +420 -0
- package/dist/dialects/turso/index.js +227 -0
- package/dist/dialects/vertica/index.js +317 -0
- package/dist/dialects/yugabytedb/index.js +424 -0
- package/dist/errors/index.js +472 -0
- package/dist/errors/utils.js +340 -0
- package/dist/errors.js +21 -0
- package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
- package/dist/extensions/catalog/mssql-features.js +147 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
- package/dist/extensions/catalog/oracle-db2-features.js +196 -0
- package/dist/extensions/catalog/postgres-extensions.js +516 -0
- package/dist/extensions/index.js +71 -0
- package/dist/extensions/types.js +13 -0
- package/dist/foreign-data.js +173 -0
- package/dist/hooks/hooks-manager.js +350 -0
- package/dist/hooks/index.js +37 -0
- package/dist/index.js +463 -0
- package/dist/logging.js +335 -0
- package/dist/migrations/index.js +40 -0
- package/dist/migrations/migration.js +196 -0
- package/dist/migrations/migrator.js +411 -0
- package/dist/migrations/prormmigration.js +275 -0
- package/dist/migrations/query-interface.js +435 -0
- package/dist/migrations/seeder.js +353 -0
- package/dist/models/associations.js +852 -0
- package/dist/models/constraints.js +288 -0
- package/dist/models/data-types.js +2518 -0
- package/dist/models/decorators.js +445 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexes.js +531 -0
- package/dist/models/methods.js +382 -0
- package/dist/models/model-manager.js +103 -0
- package/dist/models/model.js +5349 -0
- package/dist/models/operators.js +67 -0
- package/dist/models/scopes.js +189 -0
- package/dist/models/typescript-types.js +26 -0
- package/dist/nosql/aerospike/index.js +205 -0
- package/dist/nosql/allegrograph/index.js +169 -0
- package/dist/nosql/arangodb/index.js +364 -0
- package/dist/nosql/azure-blob/index.js +206 -0
- package/dist/nosql/beanstalkd/index.js +231 -0
- package/dist/nosql/beequeue/index.js +210 -0
- package/dist/nosql/bigchaindb/index.js +195 -0
- package/dist/nosql/bigtable/index.js +224 -0
- package/dist/nosql/blazegraph/index.js +173 -0
- package/dist/nosql/bullmq/index.js +191 -0
- package/dist/nosql/cassandra/index.js +174 -0
- package/dist/nosql/chroma/index.js +190 -0
- package/dist/nosql/cloudflare-kv/index.js +220 -0
- package/dist/nosql/coherence/index.js +200 -0
- package/dist/nosql/cosmosdb/index.js +157 -0
- package/dist/nosql/couchbase/index.js +213 -0
- package/dist/nosql/dax/index.js +212 -0
- package/dist/nosql/deno-kv/index.js +206 -0
- package/dist/nosql/dgraph/index.js +171 -0
- package/dist/nosql/doris/index.js +169 -0
- package/dist/nosql/druid/index.js +162 -0
- package/dist/nosql/dynamodb/index.js +937 -0
- package/dist/nosql/elasticsearch/index.js +377 -0
- package/dist/nosql/etcd/index.js +502 -0
- package/dist/nosql/eventhubs/index.js +213 -0
- package/dist/nosql/eventstore/index.js +254 -0
- package/dist/nosql/faunadb/index.js +188 -0
- package/dist/nosql/firestore/index.js +177 -0
- package/dist/nosql/fluree/index.js +148 -0
- package/dist/nosql/fuseki/index.js +170 -0
- package/dist/nosql/gcs/index.js +172 -0
- package/dist/nosql/gearman/index.js +160 -0
- package/dist/nosql/geode/index.js +196 -0
- package/dist/nosql/graphdb/index.js +169 -0
- package/dist/nosql/graylog/index.js +188 -0
- package/dist/nosql/gridgain/index.js +171 -0
- package/dist/nosql/hazelcast/index.js +162 -0
- package/dist/nosql/hbase/index.js +230 -0
- package/dist/nosql/ignite/index.js +173 -0
- package/dist/nosql/immudb/index.js +184 -0
- package/dist/nosql/index.js +232 -0
- package/dist/nosql/infinispan/index.js +200 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.js +234 -0
- package/dist/nosql/keyspaces/index.js +189 -0
- package/dist/nosql/kinesis/index.js +253 -0
- package/dist/nosql/leveldb/index.js +153 -0
- package/dist/nosql/lmdb/index.js +160 -0
- package/dist/nosql/loki/index.js +201 -0
- package/dist/nosql/marklogic/index.js +204 -0
- package/dist/nosql/materialize/index.js +145 -0
- package/dist/nosql/meilisearch/index.js +154 -0
- package/dist/nosql/memcached/index.js +223 -0
- package/dist/nosql/milvus/index.js +410 -0
- package/dist/nosql/minio/index.js +265 -0
- package/dist/nosql/momento/index.js +179 -0
- package/dist/nosql/mongodb/index.js +461 -0
- package/dist/nosql/nats/index.js +247 -0
- package/dist/nosql/nedb/index.js +164 -0
- package/dist/nosql/neo4j/index.js +450 -0
- package/dist/nosql/neptune/index.js +470 -0
- package/dist/nosql/nsq/index.js +200 -0
- package/dist/nosql/opensearch/index.js +186 -0
- package/dist/nosql/orientdb/index.js +175 -0
- package/dist/nosql/papertrail/index.js +200 -0
- package/dist/nosql/pinecone/index.js +0 -0
- package/dist/nosql/pinot/index.js +133 -0
- package/dist/nosql/pouchdb/index.js +172 -0
- package/dist/nosql/prometheus/index.js +174 -0
- package/dist/nosql/provendb/index.js +147 -0
- package/dist/nosql/pubsub/index.js +187 -0
- package/dist/nosql/pulsar/index.js +232 -0
- package/dist/nosql/qdrant/index.js +295 -0
- package/dist/nosql/qldb/index.js +192 -0
- package/dist/nosql/r2/index.js +281 -0
- package/dist/nosql/rabbitmq/index.js +237 -0
- package/dist/nosql/ravendb/index.js +175 -0
- package/dist/nosql/redis/index.js +607 -0
- package/dist/nosql/redpanda/index.js +237 -0
- package/dist/nosql/resque/index.js +203 -0
- package/dist/nosql/rethinkdb/index.js +232 -0
- package/dist/nosql/rocksdb/index.js +152 -0
- package/dist/nosql/rockset/index.js +126 -0
- package/dist/nosql/s3/index.js +298 -0
- package/dist/nosql/scylladb/index.js +178 -0
- package/dist/nosql/signoz/index.js +227 -0
- package/dist/nosql/sns/index.js +201 -0
- package/dist/nosql/solr/index.js +231 -0
- package/dist/nosql/splunk/index.js +227 -0
- package/dist/nosql/sqs/index.js +246 -0
- package/dist/nosql/stardog/index.js +169 -0
- package/dist/nosql/starrocks/index.js +170 -0
- package/dist/nosql/store.js +2 -0
- package/dist/nosql/sumologic/index.js +213 -0
- package/dist/nosql/surrealdb/index.js +179 -0
- package/dist/nosql/terminusdb/index.js +151 -0
- package/dist/nosql/tigergraph/index.js +469 -0
- package/dist/nosql/typesense/index.js +148 -0
- package/dist/nosql/unqlite/index.js +157 -0
- package/dist/nosql/upstash/index.js +167 -0
- package/dist/nosql/vercel-kv/index.js +166 -0
- package/dist/nosql/victoriametrics/index.js +213 -0
- package/dist/nosql/virtuoso/index.js +169 -0
- package/dist/nosql/weaviate/index.js +276 -0
- package/dist/operators/index.js +108 -0
- package/dist/operators.js +2690 -0
- package/dist/prisma-migrate/index.js +38 -0
- package/dist/prisma-migrate/migration-generator.js +125 -0
- package/dist/prisma-migrate/model-generator.js +172 -0
- package/dist/prisma-migrate/relations.js +100 -0
- package/dist/prisma-migrate/schema-parser.js +167 -0
- package/dist/prisma-migrate/type-mapper.js +40 -0
- package/dist/prorm.js +6832 -0
- package/dist/query-builders/cte-builder.js +80 -0
- package/dist/query-builders/functions/aggregate.js +390 -0
- package/dist/query-builders/functions/conditional.js +503 -0
- package/dist/query-builders/functions/datetime.js +695 -0
- package/dist/query-builders/functions/fulltext.js +439 -0
- package/dist/query-builders/functions/index.js +93 -0
- package/dist/query-builders/functions/json.js +427 -0
- package/dist/query-builders/functions/math.js +399 -0
- package/dist/query-builders/functions/string.js +518 -0
- package/dist/query-builders/functions/window.js +328 -0
- package/dist/query-builders/include-builder.js +323 -0
- package/dist/query-builders/index-expression-builder.js +242 -0
- package/dist/query-builders/index.js +161 -0
- package/dist/query-builders/insert-builder.js +164 -0
- package/dist/query-builders/model-helpers.js +38 -0
- package/dist/query-builders/order-limit-builder.js +239 -0
- package/dist/query-builders/sql-compiler.js +2040 -0
- package/dist/query-builders/subquery-builder.js +152 -0
- package/dist/query-builders/update-builder.js +182 -0
- package/dist/query-builders/view-builder.js +218 -0
- package/dist/query-builders/where-builder.js +1532 -0
- package/dist/query-interface.js +622 -0
- package/dist/query-optimizers/batch-optimizer.js +426 -0
- package/dist/query-optimizers/explain-plans.js +415 -0
- package/dist/query-optimizers/index.js +51 -0
- package/dist/query-optimizers/prepared-statement-cache.js +423 -0
- package/dist/query-optimizers/query-hints.js +438 -0
- package/dist/query-optimizers/query-optimizer.js +278 -0
- package/dist/query-optimizers/slow-query-logger.js +271 -0
- package/dist/replica-manager.js +507 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/migration-generator.js +374 -0
- package/dist/schema/schema-differ.js +549 -0
- package/dist/schema/types.js +6 -0
- package/dist/sql-constants.js +300 -0
- package/dist/sqlite-advanced.js +1047 -0
- package/dist/streams/index.js +12 -0
- package/dist/streams/transforms.js +178 -0
- package/dist/transaction.js +456 -0
- package/dist/types/index.js +180 -0
- package/dist/user-management.js +127 -0
- package/dist/utils/date.js +300 -0
- package/dist/utils/index.js +1079 -0
- package/dist/utils/string.js +161 -0
- package/dist/validators/index.js +19 -0
- package/dist/validators/validator.js +911 -0
- package/package.json +190 -0
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite Advanced Features Module
|
|
4
|
+
*
|
|
5
|
+
* Comprehensive support for SQLite advanced features including:
|
|
6
|
+
* - Views (Standard and updatable)
|
|
7
|
+
* - Triggers (BEFORE/AFTER INSERT/UPDATE/DELETE, recursive triggers)
|
|
8
|
+
* - Foreign Tables (FTS5, carray, etc.)
|
|
9
|
+
* - Full-Text Search (FTS5) - MATCH, rank, highlighting
|
|
10
|
+
* - JSON JSON1 extension - JSON each, extract
|
|
11
|
+
* - Generated Columns - VIRTUAL, STORED
|
|
12
|
+
* - Indexes - Partial, UNIQUE, expression indexes
|
|
13
|
+
* - Window Functions - SQLite 3.25+ (ROW_NUMBER, etc.)
|
|
14
|
+
* - CTEs - WITH RECURSIVE
|
|
15
|
+
* - Common Table Expressions
|
|
16
|
+
* - VACUUM and ANALYZE - Optimization
|
|
17
|
+
* - Savepoints - Transaction management
|
|
18
|
+
* - UPSERT - INSERT ... ON CONFLICT
|
|
19
|
+
* - Table-valued Functions - json_each, fts5, etc.
|
|
20
|
+
* - Extensions - Loadable extensions
|
|
21
|
+
* - SQLite-specific Types - INTEGER PRIMARY KEY, affinity
|
|
22
|
+
* - Constraints - CHECK, UNIQUE, NOT NULL with ON CONFLICT
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.SQLiteAdvanced = void 0;
|
|
26
|
+
exports.createSQLiteAdvanced = createSQLiteAdvanced;
|
|
27
|
+
/**
|
|
28
|
+
* SQLite Advanced Features Class
|
|
29
|
+
* Provides comprehensive SQLite-specific features beyond standard SQL
|
|
30
|
+
*/
|
|
31
|
+
class SQLiteAdvanced {
|
|
32
|
+
constructor(dialect) {
|
|
33
|
+
this.dialect = dialect;
|
|
34
|
+
this.connection = dialect.getConnection();
|
|
35
|
+
}
|
|
36
|
+
// ============================================
|
|
37
|
+
// VIEW MANAGEMENT
|
|
38
|
+
// ============================================
|
|
39
|
+
/**
|
|
40
|
+
* Create a standard or updatable view
|
|
41
|
+
* SQLite views are read-only by default, but can be made updatable via INSTEAD OF triggers
|
|
42
|
+
*/
|
|
43
|
+
async createView(viewName, query, options) {
|
|
44
|
+
let sql = 'CREATE ';
|
|
45
|
+
if (options?.temporary) {
|
|
46
|
+
sql += 'TEMPORARY ';
|
|
47
|
+
}
|
|
48
|
+
if (options?.replace) {
|
|
49
|
+
sql = 'CREATE OR REPLACE ';
|
|
50
|
+
}
|
|
51
|
+
sql += 'VIEW ';
|
|
52
|
+
if (options?.columns) {
|
|
53
|
+
sql += `${this.escapeId(viewName)} (${options.columns.join(', ')}) `;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
sql += `${this.escapeId(viewName)} `;
|
|
57
|
+
}
|
|
58
|
+
sql += `AS ${query}`;
|
|
59
|
+
await this.dialect.query(sql);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Drop a view
|
|
63
|
+
*/
|
|
64
|
+
async dropView(viewName, options) {
|
|
65
|
+
let sql = 'DROP VIEW';
|
|
66
|
+
if (options?.ifExists) {
|
|
67
|
+
sql += ' IF EXISTS';
|
|
68
|
+
}
|
|
69
|
+
if (options?.cascade) {
|
|
70
|
+
sql += ' CASCADE';
|
|
71
|
+
}
|
|
72
|
+
sql += ` ${this.escapeId(viewName)}`;
|
|
73
|
+
await this.dialect.query(sql);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* List all views in the database
|
|
77
|
+
*/
|
|
78
|
+
async listViews(schema) {
|
|
79
|
+
const sql = schema
|
|
80
|
+
? "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%' AND name LIKE ?"
|
|
81
|
+
: "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%'";
|
|
82
|
+
const result = await this.dialect.query(sql, schema ? { replacements: [schema] } : undefined);
|
|
83
|
+
return result.rows.map((row) => row.name);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get view definition
|
|
87
|
+
*/
|
|
88
|
+
async getViewDefinition(viewName) {
|
|
89
|
+
const sql = "SELECT sql FROM sqlite_master WHERE type='view' AND name = ?";
|
|
90
|
+
const result = await this.dialect.query(sql, { replacements: [viewName] });
|
|
91
|
+
return result.rows[0]?.sql || null;
|
|
92
|
+
}
|
|
93
|
+
// ============================================
|
|
94
|
+
// TRIGGER MANAGEMENT
|
|
95
|
+
// ============================================
|
|
96
|
+
/**
|
|
97
|
+
* Create a trigger
|
|
98
|
+
*/
|
|
99
|
+
async createTrigger(triggerName, timing, events, tableName, body, options) {
|
|
100
|
+
// SQLite does not support `CREATE OR REPLACE TRIGGER` (unlike MySQL/Postgres).
|
|
101
|
+
// Emulate "replace" semantics by dropping any existing trigger with the same
|
|
102
|
+
// name first, then creating the new one.
|
|
103
|
+
if (options?.replace) {
|
|
104
|
+
await this.dropTrigger(triggerName, { ifExists: true });
|
|
105
|
+
}
|
|
106
|
+
let sql = 'CREATE TRIGGER ';
|
|
107
|
+
sql += this.escapeId(triggerName);
|
|
108
|
+
sql += ` ${timing} ${(events || []).join(' OR ')} ON ${this.escapeId(tableName)}`;
|
|
109
|
+
if (options?.forEachRow) {
|
|
110
|
+
sql += ' FOR EACH ROW';
|
|
111
|
+
}
|
|
112
|
+
if (options?.whenCondition) {
|
|
113
|
+
sql += ` WHEN ${options.whenCondition}`;
|
|
114
|
+
}
|
|
115
|
+
sql += `\nBEGIN\n${body}\nEND`;
|
|
116
|
+
await this.dialect.query(sql);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Create an AFTER INSERT trigger
|
|
120
|
+
*/
|
|
121
|
+
async createAfterInsertTrigger(triggerName, tableName, body) {
|
|
122
|
+
await this.createTrigger(triggerName, 'AFTER', ['INSERT'], tableName, body, {
|
|
123
|
+
forEachRow: true,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create an AFTER UPDATE trigger
|
|
128
|
+
*/
|
|
129
|
+
async createAfterUpdateTrigger(triggerName, tableName, body, columns) {
|
|
130
|
+
const events = columns ? ['UPDATE OF ' + columns.join(', ')] : ['UPDATE'];
|
|
131
|
+
await this.createTrigger(triggerName, 'AFTER', events, tableName, body, {
|
|
132
|
+
forEachRow: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create an AFTER DELETE trigger
|
|
137
|
+
*/
|
|
138
|
+
async createAfterDeleteTrigger(triggerName, tableName, body) {
|
|
139
|
+
await this.createTrigger(triggerName, 'AFTER', ['DELETE'], tableName, body, {
|
|
140
|
+
forEachRow: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Create a BEFORE INSERT trigger
|
|
145
|
+
*/
|
|
146
|
+
async createBeforeInsertTrigger(triggerName, tableName, body) {
|
|
147
|
+
await this.createTrigger(triggerName, 'BEFORE', ['INSERT'], tableName, body, {
|
|
148
|
+
forEachRow: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a BEFORE UPDATE trigger
|
|
153
|
+
*/
|
|
154
|
+
async createBeforeUpdateTrigger(triggerName, tableName, body) {
|
|
155
|
+
await this.createTrigger(triggerName, 'BEFORE', ['UPDATE'], tableName, body, {
|
|
156
|
+
forEachRow: true,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Create an INSTEAD OF trigger (for views)
|
|
161
|
+
*/
|
|
162
|
+
async createInsteadOfTrigger(triggerName, viewName, events, body) {
|
|
163
|
+
await this.createTrigger(triggerName, 'INSTEAD OF', events, viewName, body, {
|
|
164
|
+
forEachRow: true,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create a recursive trigger
|
|
169
|
+
*/
|
|
170
|
+
async createRecursiveTrigger(triggerName, tableName, insertBody, updateBody, deleteBody) {
|
|
171
|
+
// Create INSERT trigger
|
|
172
|
+
await this.createTrigger(`${triggerName}_insert`, 'AFTER', ['INSERT'], tableName, insertBody, {
|
|
173
|
+
forEachRow: true,
|
|
174
|
+
});
|
|
175
|
+
// Create UPDATE trigger
|
|
176
|
+
await this.createTrigger(`${triggerName}_update`, 'AFTER', ['UPDATE'], tableName, updateBody, {
|
|
177
|
+
forEachRow: true,
|
|
178
|
+
});
|
|
179
|
+
// Create DELETE trigger if provided
|
|
180
|
+
if (deleteBody) {
|
|
181
|
+
await this.createTrigger(`${triggerName}_delete`, 'AFTER', ['DELETE'], tableName, deleteBody, { forEachRow: true });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Drop a trigger
|
|
186
|
+
*/
|
|
187
|
+
async dropTrigger(triggerName, options) {
|
|
188
|
+
let sql = 'DROP TRIGGER';
|
|
189
|
+
if (options?.ifExists) {
|
|
190
|
+
sql += ' IF EXISTS';
|
|
191
|
+
}
|
|
192
|
+
sql += ` ${this.escapeId(triggerName)}`;
|
|
193
|
+
await this.dialect.query(sql);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* List all triggers
|
|
197
|
+
*/
|
|
198
|
+
async listTriggers(tableName) {
|
|
199
|
+
const sql = tableName
|
|
200
|
+
? "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name = ?"
|
|
201
|
+
: "SELECT name FROM sqlite_master WHERE type='trigger' AND name NOT LIKE 'sqlite_%'";
|
|
202
|
+
const finalSql = tableName ? this.dialect.replaceReplacements(sql, [tableName]) : sql;
|
|
203
|
+
const result = await this.dialect.query(finalSql);
|
|
204
|
+
return result.rows.map((row) => row.name);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get trigger definition
|
|
208
|
+
*/
|
|
209
|
+
async getTriggerDefinition(triggerName) {
|
|
210
|
+
const sql = "SELECT sql FROM sqlite_master WHERE type='trigger' AND name = ?";
|
|
211
|
+
const result = await this.dialect.query(sql, { replacements: [triggerName] });
|
|
212
|
+
return result.rows[0]?.sql || null;
|
|
213
|
+
}
|
|
214
|
+
// ============================================
|
|
215
|
+
// FULL-TEXT SEARCH (FTS5)
|
|
216
|
+
// ============================================
|
|
217
|
+
/**
|
|
218
|
+
* Create an FTS5 virtual table for full-text search
|
|
219
|
+
*/
|
|
220
|
+
async createFTS5Table(tableName, columns, options) {
|
|
221
|
+
const columnDefs = columns
|
|
222
|
+
.map((col) => {
|
|
223
|
+
let def = this.escapeId(col.name);
|
|
224
|
+
if (col.type) {
|
|
225
|
+
def += ` ${col.type}`;
|
|
226
|
+
}
|
|
227
|
+
if (col.notindexed) {
|
|
228
|
+
def += ' NOTINDEXED';
|
|
229
|
+
}
|
|
230
|
+
return def;
|
|
231
|
+
})
|
|
232
|
+
.join(', ');
|
|
233
|
+
let sql = `CREATE VIRTUAL TABLE ${this.escapeId(tableName)} USING fts5(${columnDefs}`;
|
|
234
|
+
if (options?.tokenizer) {
|
|
235
|
+
if (typeof options.tokenizer === 'string') {
|
|
236
|
+
sql += `, tokenize='${options.tokenizer}'`;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const opts = options.tokenizer.options
|
|
240
|
+
? `, ${Object.entries(options.tokenizer.options)
|
|
241
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
242
|
+
.join(', ')}`
|
|
243
|
+
: '';
|
|
244
|
+
sql += `, tokenize='${options.tokenizer.name}${opts}'`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (options?.content) {
|
|
248
|
+
sql += `, content='${options.content}'`;
|
|
249
|
+
}
|
|
250
|
+
if (options?.contentRowId) {
|
|
251
|
+
sql += `, content_rowid='${options.contentRowId}'`;
|
|
252
|
+
}
|
|
253
|
+
if (options?.prefix) {
|
|
254
|
+
if (Array.isArray(options.prefix)) {
|
|
255
|
+
sql += `, prefix='${options.prefix.join(' ')}'`;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
sql += `, prefix='${options.prefix}'`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
sql += ')';
|
|
262
|
+
await this.dialect.query(sql);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Create FTS5 table with existing content table
|
|
266
|
+
*/
|
|
267
|
+
async createFTS5FromTable(ftsTableName, sourceTable, sourceColumns, options) {
|
|
268
|
+
// Create FTS5 virtual table
|
|
269
|
+
await this.createFTS5Table(ftsTableName, sourceColumns.map((col) => ({ name: col })), options);
|
|
270
|
+
// Populate with data from source table
|
|
271
|
+
const columnsStr = sourceColumns.map((c) => this.escapeId(c)).join(', ');
|
|
272
|
+
const insertSql = `INSERT INTO ${this.escapeId(ftsTableName)} (${columnsStr}) SELECT ${columnsStr} FROM ${this.escapeId(sourceTable)}`;
|
|
273
|
+
await this.dialect.query(insertSql);
|
|
274
|
+
// Create triggers to keep FTS table in sync
|
|
275
|
+
await this.createTrigger(`${ftsTableName}_ai`, 'AFTER', ['INSERT'], sourceTable, `BEGIN\nINSERT INTO ${this.escapeId(ftsTableName)} (${columnsStr}) VALUES (new.${sourceColumns.join(', new.')});\nEND`);
|
|
276
|
+
await this.createTrigger(`${ftsTableName}_ad`, 'AFTER', ['DELETE'], sourceTable, `BEGIN\nDELETE FROM ${this.escapeId(ftsTableName)} WHERE rowid = old.rowid;\nEND`);
|
|
277
|
+
const setClauses = sourceColumns.map((col) => `${col} = new.${col}`).join(', ');
|
|
278
|
+
await this.createTrigger(`${ftsTableName}_au`, 'AFTER', ['UPDATE'], sourceTable, `BEGIN\nUPDATE ${this.escapeId(ftsTableName)} SET ${setClauses} WHERE rowid = old.rowid;\nEND`);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Drop an FTS5 table
|
|
282
|
+
*/
|
|
283
|
+
async dropFTS5Table(tableName, options) {
|
|
284
|
+
let sql = 'DROP TABLE';
|
|
285
|
+
if (options?.ifExists) {
|
|
286
|
+
sql += ' IF EXISTS';
|
|
287
|
+
}
|
|
288
|
+
sql += ` ${this.escapeId(tableName)}`;
|
|
289
|
+
await this.dialect.query(sql);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Perform full-text search
|
|
293
|
+
*/
|
|
294
|
+
async fullTextSearch(ftsTable, searchQuery, options) {
|
|
295
|
+
let sql = 'SELECT';
|
|
296
|
+
if (options?.bm25) {
|
|
297
|
+
sql += ` bm25(${this.escapeId(ftsTable)}) AS score, `;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
sql += ' 0 AS score, ';
|
|
301
|
+
}
|
|
302
|
+
if (options?.highlight) {
|
|
303
|
+
const col = options.highlight.column || '*';
|
|
304
|
+
sql += ` highlight(${this.escapeId(ftsTable)}, ${options.columns?.indexOf(col) ?? 0}${options.highlight.before ? `, '${options.highlight.before}'` : ''}${options.highlight.after ? `, '${options.highlight.after}'` : ''}) AS highlighted, `;
|
|
305
|
+
}
|
|
306
|
+
sql += options?.columns?.map((c) => this.escapeId(c)).join(', ') || '*';
|
|
307
|
+
sql += ` FROM ${this.escapeId(ftsTable)}`;
|
|
308
|
+
sql += ` WHERE ${this.escapeId(ftsTable)} MATCH ?`;
|
|
309
|
+
if (options?.orderBy) {
|
|
310
|
+
sql += ` ORDER BY ${options.orderBy.map((o) => `${this.escapeId(o.column)}${o.desc ? ' DESC' : ''}`).join(', ')}`;
|
|
311
|
+
}
|
|
312
|
+
if (options?.limit) {
|
|
313
|
+
sql += ` LIMIT ${options.limit}`;
|
|
314
|
+
}
|
|
315
|
+
if (options?.offset) {
|
|
316
|
+
sql += ` OFFSET ${options.offset}`;
|
|
317
|
+
}
|
|
318
|
+
return this.dialect.query(sql, { replacements: [searchQuery] });
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Rebuild FTS5 index
|
|
322
|
+
*/
|
|
323
|
+
async rebuildFTS5(tableName) {
|
|
324
|
+
await this.dialect.query(`INSERT INTO ${this.escapeId(tableName)}(${this.escapeId(tableName)}) VALUES('rebuild')`);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Optimize FTS5 index
|
|
328
|
+
*/
|
|
329
|
+
async optimizeFTS5(tableName) {
|
|
330
|
+
await this.dialect.query(`INSERT INTO ${this.escapeId(tableName)}(${this.escapeId(tableName)}) VALUES('optimize')`);
|
|
331
|
+
}
|
|
332
|
+
// ============================================
|
|
333
|
+
// JSON JSON1 EXTENSION
|
|
334
|
+
// ============================================
|
|
335
|
+
/**
|
|
336
|
+
* Parse JSON string
|
|
337
|
+
*/
|
|
338
|
+
parseJson(jsonString) {
|
|
339
|
+
try {
|
|
340
|
+
return JSON.parse(jsonString);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Extract value from JSON
|
|
348
|
+
*/
|
|
349
|
+
jsonExtract(jsonColumn, path) {
|
|
350
|
+
return `json_extract(${jsonColumn}, '${this.escapePathLiteral(path)}')`;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Set value in JSON
|
|
354
|
+
*/
|
|
355
|
+
jsonSet(jsonColumn, path, value) {
|
|
356
|
+
return `json_set(${jsonColumn}, '${this.escapePathLiteral(path)}', ${this.escape(value)})`;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Insert into JSON array
|
|
360
|
+
*/
|
|
361
|
+
jsonArrayAppend(jsonColumn, path, value) {
|
|
362
|
+
return `json_array_append(${jsonColumn}, '${this.escapePathLiteral(path)}', ${this.escape(value)})`;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Remove from JSON
|
|
366
|
+
*/
|
|
367
|
+
jsonRemove(jsonColumn, path) {
|
|
368
|
+
return `json_remove(${jsonColumn}, '${this.escapePathLiteral(path)}')`;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get JSON array length
|
|
372
|
+
*/
|
|
373
|
+
jsonArrayLength(jsonColumn, path) {
|
|
374
|
+
return path
|
|
375
|
+
? `json_array_length(${jsonColumn}, '${this.escapePathLiteral(path)}')`
|
|
376
|
+
: `json_array_length(${jsonColumn})`;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Check if JSON contains value
|
|
380
|
+
*
|
|
381
|
+
* Note: SQLite's JSON1 extension has no `json_contains` function (that name is
|
|
382
|
+
* MySQL-specific); calling this would fail at runtime with "no such function:
|
|
383
|
+
* json_contains". Emulate it with json_extract() equality instead.
|
|
384
|
+
*/
|
|
385
|
+
jsonContains(jsonColumn, value, path) {
|
|
386
|
+
const target = path ? `json_extract(${jsonColumn}, '${this.escapePathLiteral(path)}')` : jsonColumn;
|
|
387
|
+
return `${target} = ${this.escape(typeof value === 'string' ? value : JSON.stringify(value))}`;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Query JSON array as table
|
|
391
|
+
*/
|
|
392
|
+
jsonEach(jsonColumn, path) {
|
|
393
|
+
return path ? `json_each(${jsonColumn}, '${this.escapePathLiteral(path)}')` : `json_each(${jsonColumn})`;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Query JSON object as table
|
|
397
|
+
*/
|
|
398
|
+
jsonTree(jsonColumn, path) {
|
|
399
|
+
return path ? `json_tree(${jsonColumn}, '${this.escapePathLiteral(path)}')` : `json_tree(${jsonColumn})`;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get JSON type
|
|
403
|
+
*/
|
|
404
|
+
jsonType(jsonColumn, path) {
|
|
405
|
+
return path ? `json_type(${jsonColumn}, '${this.escapePathLiteral(path)}')` : `json_type(${jsonColumn})`;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Valid JSON check
|
|
409
|
+
*/
|
|
410
|
+
jsonValid(jsonColumn) {
|
|
411
|
+
return `json_valid(${jsonColumn})`;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Query using json_each (table-valued function)
|
|
415
|
+
*/
|
|
416
|
+
async jsonEachQuery(jsonColumn, path, options) {
|
|
417
|
+
const tableRef = path ? `json_each(${jsonColumn}, '${path}')` : `json_each(${jsonColumn})`;
|
|
418
|
+
let sql = `SELECT * FROM ${tableRef}`;
|
|
419
|
+
if (options?.key || options?.value || options?.type) {
|
|
420
|
+
const cols = [];
|
|
421
|
+
if (options?.key)
|
|
422
|
+
cols.push(`key AS ${options.key}`);
|
|
423
|
+
if (options?.value)
|
|
424
|
+
cols.push(`value AS ${options.value}`);
|
|
425
|
+
if (options?.type)
|
|
426
|
+
cols.push(`type AS ${options.type}`);
|
|
427
|
+
if (options?.id)
|
|
428
|
+
cols.push(`id AS ${options.id}`);
|
|
429
|
+
if (options?.parent)
|
|
430
|
+
cols.push(`parent AS ${options.parent}`);
|
|
431
|
+
sql = `SELECT ${cols.join(', ')} FROM ${tableRef}`;
|
|
432
|
+
}
|
|
433
|
+
return this.dialect.query(sql);
|
|
434
|
+
}
|
|
435
|
+
// ============================================
|
|
436
|
+
// GENERATED COLUMNS
|
|
437
|
+
// ============================================
|
|
438
|
+
/**
|
|
439
|
+
* Create table with generated columns
|
|
440
|
+
*/
|
|
441
|
+
createTableWithGeneratedColumns(tableName, columns) {
|
|
442
|
+
const columnDefs = columns
|
|
443
|
+
.map((col) => {
|
|
444
|
+
let def = `${this.escapeId(col.name)} ${col.type}`;
|
|
445
|
+
if (col.primaryKey) {
|
|
446
|
+
def += ' PRIMARY KEY';
|
|
447
|
+
}
|
|
448
|
+
if (col.notNull && !col.generated) {
|
|
449
|
+
def += ' NOT NULL';
|
|
450
|
+
}
|
|
451
|
+
if (col.defaultValue !== undefined && !col.generated) {
|
|
452
|
+
def += ` DEFAULT ${this.escape(col.defaultValue)}`;
|
|
453
|
+
}
|
|
454
|
+
if (col.generated) {
|
|
455
|
+
def += ` GENERATED ALWAYS AS (${col.generated.expression}) ${col.generated.type}`;
|
|
456
|
+
}
|
|
457
|
+
return def;
|
|
458
|
+
})
|
|
459
|
+
.join(', ');
|
|
460
|
+
return `CREATE TABLE ${this.escapeId(tableName)} (${columnDefs})`;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Create a VIRTUAL generated column
|
|
464
|
+
*/
|
|
465
|
+
createVirtualColumn(name, type, expression) {
|
|
466
|
+
return `${this.escapeId(name)} ${type} GENERATED ALWAYS AS (${expression}) VIRTUAL`;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Create a STORED generated column
|
|
470
|
+
*/
|
|
471
|
+
createStoredColumn(name, type, expression) {
|
|
472
|
+
return `${this.escapeId(name)} ${type} GENERATED ALWAYS AS (${expression}) STORED`;
|
|
473
|
+
}
|
|
474
|
+
// ============================================
|
|
475
|
+
// INDEXES - ADVANCED
|
|
476
|
+
// ============================================
|
|
477
|
+
/**
|
|
478
|
+
* Create a UNIQUE index
|
|
479
|
+
*/
|
|
480
|
+
async createUniqueIndex(tableName, indexName, columns) {
|
|
481
|
+
const sql = `CREATE UNIQUE INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')})`;
|
|
482
|
+
await this.dialect.query(sql);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Create a partial index (with WHERE clause)
|
|
486
|
+
*/
|
|
487
|
+
async createPartialIndex(tableName, indexName, columns, whereCondition) {
|
|
488
|
+
const sql = `CREATE INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) WHERE ${whereCondition}`;
|
|
489
|
+
await this.dialect.query(sql);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Create an expression index
|
|
493
|
+
*/
|
|
494
|
+
async createExpressionIndex(tableName, indexName, expression) {
|
|
495
|
+
const sql = `CREATE INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${expression})`;
|
|
496
|
+
await this.dialect.query(sql);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Create an index with IF NOT EXISTS
|
|
500
|
+
*/
|
|
501
|
+
async createIndexIfNotExists(tableName, indexName, columns, options) {
|
|
502
|
+
const sql = `CREATE INDEX IF NOT EXISTS ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')})${options?.where ? ` WHERE ${options.where}` : ''}`;
|
|
503
|
+
await this.dialect.query(sql);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* List all indexes for a table
|
|
507
|
+
*/
|
|
508
|
+
async listIndexes(tableName) {
|
|
509
|
+
const sql = `SELECT name, sql FROM sqlite_master WHERE type='index' AND tbl_name=? AND name NOT LIKE 'sqlite_%'`;
|
|
510
|
+
const result = await this.dialect.query(sql, { replacements: [tableName] });
|
|
511
|
+
return result.rows.map((row) => ({
|
|
512
|
+
name: row.name,
|
|
513
|
+
unique: row.sql?.toUpperCase().includes('UNIQUE') || false,
|
|
514
|
+
partial: row.sql?.includes('WHERE') || false,
|
|
515
|
+
}));
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Reindex a table or database
|
|
519
|
+
*/
|
|
520
|
+
async reindex(target) {
|
|
521
|
+
const sql = target ? `REINDEX ${this.escapeId(target)}` : 'REINDEX';
|
|
522
|
+
await this.dialect.query(sql);
|
|
523
|
+
}
|
|
524
|
+
// ============================================
|
|
525
|
+
// WINDOW FUNCTIONS
|
|
526
|
+
// ============================================
|
|
527
|
+
/**
|
|
528
|
+
* Build a window function query
|
|
529
|
+
*/
|
|
530
|
+
buildWindowFunction(functionName, options) {
|
|
531
|
+
let sql = functionName;
|
|
532
|
+
if (options?.distinct) {
|
|
533
|
+
sql = `DISTINCT ${sql}`;
|
|
534
|
+
}
|
|
535
|
+
if (options?.filter) {
|
|
536
|
+
sql += ` FILTER (WHERE ${options.filter})`;
|
|
537
|
+
}
|
|
538
|
+
if (options?.over) {
|
|
539
|
+
sql += ' OVER (';
|
|
540
|
+
if (options.over.partitionBy?.length) {
|
|
541
|
+
sql += `PARTITION BY ${options.over.partitionBy.map((c) => this.escapeId(c)).join(', ')}`;
|
|
542
|
+
}
|
|
543
|
+
if (options.over.orderBy?.length) {
|
|
544
|
+
if (options.over.partitionBy?.length)
|
|
545
|
+
sql += ' ';
|
|
546
|
+
sql += `ORDER BY ${options.over.orderBy.map((o) => `${this.escapeId(o.column)}${o.desc ? ' DESC' : ''}`).join(', ')}`;
|
|
547
|
+
}
|
|
548
|
+
if (options.over.frame) {
|
|
549
|
+
const frame = options.over.frame;
|
|
550
|
+
const rows = options.over.rows;
|
|
551
|
+
const range = options.over.range;
|
|
552
|
+
sql += ` ${frame} BETWEEN `;
|
|
553
|
+
if (rows?.start !== undefined) {
|
|
554
|
+
sql += rows.start < 0 ? `CURRENT ROW + ${rows.start}` : `${rows.start}`;
|
|
555
|
+
}
|
|
556
|
+
else if (range?.start !== undefined) {
|
|
557
|
+
sql += range.start < 0 ? `CURRENT ROW + ${range.start}` : `${range.start}`;
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
sql += 'UNBOUNDED PRECEDING';
|
|
561
|
+
}
|
|
562
|
+
sql += ' AND ';
|
|
563
|
+
if (rows?.end !== undefined) {
|
|
564
|
+
sql += rows.end < 0 ? `CURRENT ROW + ${rows.end}` : `${rows.end}`;
|
|
565
|
+
}
|
|
566
|
+
else if (range?.end !== undefined) {
|
|
567
|
+
sql += range.end < 0 ? `CURRENT ROW + ${range.end}` : `${range.end}`;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
sql += 'UNBOUNDED FOLLOWING';
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
sql += ')';
|
|
574
|
+
}
|
|
575
|
+
return sql;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* ROW_NUMBER() window function
|
|
579
|
+
*/
|
|
580
|
+
rowNumber(options) {
|
|
581
|
+
return this.buildWindowFunction('ROW_NUMBER()', { over: options });
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* RANK() window function
|
|
585
|
+
*/
|
|
586
|
+
rank(options) {
|
|
587
|
+
return this.buildWindowFunction('RANK()', { over: options });
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* DENSE_RANK() window function
|
|
591
|
+
*/
|
|
592
|
+
denseRank(options) {
|
|
593
|
+
return this.buildWindowFunction('DENSE_RANK()', { over: options });
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* NTILE(n) window function
|
|
597
|
+
*/
|
|
598
|
+
ntile(n, options) {
|
|
599
|
+
return this.buildWindowFunction(`NTILE(${n})`, { over: options });
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* LAG() window function
|
|
603
|
+
*/
|
|
604
|
+
lag(column, offset, defaultValue) {
|
|
605
|
+
const func = `LAG(${this.escapeId(column)}${offset ? `, ${offset}` : ''}${defaultValue !== undefined ? `, ${this.escape(defaultValue)}` : ''})`;
|
|
606
|
+
return this.buildWindowFunction(func);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* LEAD() window function
|
|
610
|
+
*/
|
|
611
|
+
lead(column, offset, defaultValue) {
|
|
612
|
+
const func = `LEAD(${this.escapeId(column)}${offset ? `, ${offset}` : ''}${defaultValue !== undefined ? `, ${this.escape(defaultValue)}` : ''})`;
|
|
613
|
+
return this.buildWindowFunction(func);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* FIRST_VALUE() window function
|
|
617
|
+
*/
|
|
618
|
+
firstValue(column) {
|
|
619
|
+
return this.buildWindowFunction(`FIRST_VALUE(${this.escapeId(column)})`);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* LAST_VALUE() window function
|
|
623
|
+
*/
|
|
624
|
+
lastValue(column) {
|
|
625
|
+
return this.buildWindowFunction(`LAST_VALUE(${this.escapeId(column)})`);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* NTH_VALUE() window function
|
|
629
|
+
*/
|
|
630
|
+
nthValue(column, n) {
|
|
631
|
+
return this.buildWindowFunction(`NTH_VALUE(${this.escapeId(column)}, ${n})`);
|
|
632
|
+
}
|
|
633
|
+
// ============================================
|
|
634
|
+
// COMMON TABLE EXPRESSIONS (CTEs)
|
|
635
|
+
// ============================================
|
|
636
|
+
/**
|
|
637
|
+
* Build a CTE query
|
|
638
|
+
*/
|
|
639
|
+
buildCTE(cteName, columns, query, recursive) {
|
|
640
|
+
const cols = columns.length ? `(${columns.join(', ')})` : '';
|
|
641
|
+
return `WITH ${recursive ? 'RECURSIVE ' : ''}${this.escapeId(cteName)}${cols} AS (${query})`;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Build multiple CTEs
|
|
645
|
+
*/
|
|
646
|
+
buildMultipleCTEs(ctes, mainQuery, recursive) {
|
|
647
|
+
const cteSqlParts = [];
|
|
648
|
+
for (const cte of ctes) {
|
|
649
|
+
const cols = cte.columns?.length ? `(${cte.columns.join(', ')})` : '';
|
|
650
|
+
const escapedName = this.escapeId(cte.name);
|
|
651
|
+
cteSqlParts.push(`${escapedName}${cols} AS (${cte.query})`);
|
|
652
|
+
}
|
|
653
|
+
const cteSql = cteSqlParts.join(', ');
|
|
654
|
+
const withPrefix = recursive ? 'WITH RECURSIVE ' : 'WITH ';
|
|
655
|
+
return `${withPrefix}${cteSql} ${mainQuery}`;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Execute a recursive CTE query
|
|
659
|
+
*/
|
|
660
|
+
async executeRecursiveCTE(cteName, baseQuery, recursiveQuery, finalQuery) {
|
|
661
|
+
const sql = `WITH RECURSIVE ${this.escapeId(cteName)} AS (${baseQuery} UNION ALL ${recursiveQuery}) ${finalQuery}`;
|
|
662
|
+
return this.dialect.query(sql);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Recursive CTE example: Generate sequence
|
|
666
|
+
*/
|
|
667
|
+
generateSequence(start, end) {
|
|
668
|
+
return `WITH RECURSIVE cnt(x) AS (
|
|
669
|
+
SELECT ${start}
|
|
670
|
+
UNION ALL
|
|
671
|
+
SELECT x + 1 FROM cnt WHERE x < ${end}
|
|
672
|
+
) SELECT x FROM cnt`;
|
|
673
|
+
}
|
|
674
|
+
// ============================================
|
|
675
|
+
// VACUUM AND ANALYZE
|
|
676
|
+
// ============================================
|
|
677
|
+
/**
|
|
678
|
+
* VACUUM the database
|
|
679
|
+
* Reclaims disk space and defragments the database
|
|
680
|
+
*/
|
|
681
|
+
async vacuum(options) {
|
|
682
|
+
let sql = 'VACUUM';
|
|
683
|
+
if (options?.schema) {
|
|
684
|
+
sql += ` ${options.schema}`;
|
|
685
|
+
}
|
|
686
|
+
if (options?.shrinkTo !== undefined) {
|
|
687
|
+
sql += ` INTO 'shrink_${options.shrinkTo}.db'`;
|
|
688
|
+
}
|
|
689
|
+
await this.dialect.query(sql);
|
|
690
|
+
if (options?.analyze) {
|
|
691
|
+
await this.analyze();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* ANALYZE the database
|
|
696
|
+
* Updates statistics for the query optimizer
|
|
697
|
+
*/
|
|
698
|
+
async analyze(target) {
|
|
699
|
+
const sql = target ? `ANALYZE ${this.escapeId(target)}` : 'ANALYZE';
|
|
700
|
+
await this.dialect.query(sql);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* ANALYZE with detailed information
|
|
704
|
+
*/
|
|
705
|
+
async analyzeDetailed(tableName) {
|
|
706
|
+
const sql = tableName ? `ANALYZE ${this.escapeId(tableName)}` : 'ANALYZE';
|
|
707
|
+
await this.dialect.query(sql);
|
|
708
|
+
if (tableName) {
|
|
709
|
+
const statSql = this.dialect.replaceReplacements(`SELECT * FROM sqlite_stat1 WHERE tbl = ?`, [tableName]);
|
|
710
|
+
return this.dialect.query(statSql);
|
|
711
|
+
}
|
|
712
|
+
return this.dialect.query(`SELECT * FROM sqlite_stat1`);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get table statistics
|
|
716
|
+
*/
|
|
717
|
+
async getTableStats(tableName) {
|
|
718
|
+
const sql = `SELECT * FROM sqlite_stat1 WHERE tbl = ?`;
|
|
719
|
+
const finalSql = this.dialect.replaceReplacements(sql, [tableName]);
|
|
720
|
+
return this.dialect.query(finalSql);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Get index statistics
|
|
724
|
+
*/
|
|
725
|
+
async getIndexStats(indexName) {
|
|
726
|
+
const sql = `SELECT * FROM sqlite_stat1 WHERE idx = ?`;
|
|
727
|
+
const finalSql = this.dialect.replaceReplacements(sql, [indexName]);
|
|
728
|
+
return this.dialect.query(finalSql);
|
|
729
|
+
}
|
|
730
|
+
// ============================================
|
|
731
|
+
// SAVEPOINTS AND TRANSACTION MANAGEMENT
|
|
732
|
+
// ============================================
|
|
733
|
+
/**
|
|
734
|
+
* Create a savepoint
|
|
735
|
+
*/
|
|
736
|
+
async savepoint(savepointName) {
|
|
737
|
+
await this.dialect.query(`SAVEPOINT ${this.escapeId(savepointName)}`);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Release a savepoint
|
|
741
|
+
*/
|
|
742
|
+
async releaseSavepoint(savepointName) {
|
|
743
|
+
await this.dialect.query(`RELEASE SAVEPOINT ${this.escapeId(savepointName)}`);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Rollback to a savepoint
|
|
747
|
+
*/
|
|
748
|
+
async rollbackToSavepoint(savepointName) {
|
|
749
|
+
await this.dialect.query(`ROLLBACK TO SAVEPOINT ${this.escapeId(savepointName)}`);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Execute within a savepoint
|
|
753
|
+
*/
|
|
754
|
+
async withSavepoint(savepointName, fn) {
|
|
755
|
+
try {
|
|
756
|
+
await this.savepoint(savepointName);
|
|
757
|
+
return await fn();
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
await this.rollbackToSavepoint(savepointName);
|
|
761
|
+
throw error;
|
|
762
|
+
}
|
|
763
|
+
finally {
|
|
764
|
+
try {
|
|
765
|
+
await this.releaseSavepoint(savepointName);
|
|
766
|
+
}
|
|
767
|
+
catch {
|
|
768
|
+
// Ignore release errors
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// ============================================
|
|
773
|
+
// UPSERT (INSERT ON CONFLICT)
|
|
774
|
+
// ============================================
|
|
775
|
+
/**
|
|
776
|
+
* Build an UPSERT query
|
|
777
|
+
*/
|
|
778
|
+
buildUpsertQuery(tableName, columns, conflictColumns, updateColumns, options) {
|
|
779
|
+
if (options?.ignore) {
|
|
780
|
+
const sql = `INSERT OR IGNORE INTO ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
781
|
+
return sql;
|
|
782
|
+
}
|
|
783
|
+
if (options?.replace) {
|
|
784
|
+
const sql = `INSERT OR REPLACE INTO ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
785
|
+
return sql;
|
|
786
|
+
}
|
|
787
|
+
const conflictCols = conflictColumns.map((c) => this.escapeId(c)).join(', ');
|
|
788
|
+
const setClause = (updateColumns || columns.filter((c) => !conflictColumns.includes(c)))
|
|
789
|
+
.map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`)
|
|
790
|
+
.join(', ');
|
|
791
|
+
return `INSERT INTO ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')}) ON CONFLICT (${conflictCols}) DO UPDATE SET ${setClause}`;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Perform an upsert
|
|
795
|
+
*/
|
|
796
|
+
async upsert(tableName, values, conflictColumns, updateColumns) {
|
|
797
|
+
const columns = Object.keys(values);
|
|
798
|
+
const sql = this.buildUpsertQuery(tableName, columns, conflictColumns, updateColumns);
|
|
799
|
+
const finalSql = this.dialect.replaceReplacements(sql, Object.values(values));
|
|
800
|
+
return this.dialect.query(finalSql);
|
|
801
|
+
}
|
|
802
|
+
// ============================================
|
|
803
|
+
// TABLE-VALUED FUNCTIONS
|
|
804
|
+
// ============================================
|
|
805
|
+
/**
|
|
806
|
+
* Use json_each as table-valued function
|
|
807
|
+
*/
|
|
808
|
+
async jsonEachAsTable(jsonColumn, path) {
|
|
809
|
+
const tableRef = path ? `json_each(${jsonColumn}, '${path}')` : `json_each(${jsonColumn})`;
|
|
810
|
+
return this.dialect.query(`SELECT * FROM ${tableRef}`);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Use json_tree as table-valued function
|
|
814
|
+
*/
|
|
815
|
+
async jsonTreeAsTable(jsonColumn, path) {
|
|
816
|
+
const tableRef = path ? `json_tree(${jsonColumn}, '${path}')` : `json_tree(${jsonColumn})`;
|
|
817
|
+
return this.dialect.query(`SELECT * FROM ${tableRef}`);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Generate series using recursive CTE
|
|
821
|
+
*/
|
|
822
|
+
async generateSeries(start, end, step) {
|
|
823
|
+
const stepStr = step ? `, ${step}` : '';
|
|
824
|
+
return this.dialect.query(`WITH RECURSIVE range(x) AS (SELECT ${start} UNION ALL SELECT x + ${step || 1} FROM range WHERE x < ${end}) SELECT x FROM range`);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Use fts5 as table-valued function
|
|
828
|
+
*/
|
|
829
|
+
async fts5Search(ftsTable, query) {
|
|
830
|
+
const sql = `SELECT * FROM ${this.escapeId(ftsTable)} WHERE ${this.escapeId(ftsTable)} MATCH ?`;
|
|
831
|
+
const finalSql = this.dialect.replaceReplacements(sql, [query]);
|
|
832
|
+
return this.dialect.query(finalSql);
|
|
833
|
+
}
|
|
834
|
+
// ============================================
|
|
835
|
+
// EXTENSIONS
|
|
836
|
+
// ============================================
|
|
837
|
+
/**
|
|
838
|
+
* Load a SQLite extension
|
|
839
|
+
*/
|
|
840
|
+
loadExtension(extensionPath) {
|
|
841
|
+
// Note: entryPoint parameter may not be supported by all extensions
|
|
842
|
+
this.connection.loadExtension(extensionPath);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Enable/disable extension loading
|
|
846
|
+
*/
|
|
847
|
+
setExtensionLoading(enabled) {
|
|
848
|
+
this.connection.pragma(`enable_load_extension = ${enabled ? 1 : 0}`);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Get list of loaded extensions
|
|
852
|
+
*/
|
|
853
|
+
getLoadedExtensions() {
|
|
854
|
+
const stmt = this.connection.prepare('SELECT * FROM pragma_compile_options WHERE compile_options LIKE "%ENABLE%"');
|
|
855
|
+
const rows = stmt.all();
|
|
856
|
+
return rows.map((row) => row.compile_options);
|
|
857
|
+
}
|
|
858
|
+
// ============================================
|
|
859
|
+
// SQLITE-SPECIFIC TYPES
|
|
860
|
+
// ============================================
|
|
861
|
+
/**
|
|
862
|
+
* Create INTEGER PRIMARY KEY column definition
|
|
863
|
+
*/
|
|
864
|
+
createIntegerPrimaryKey(columnName, autoIncrement) {
|
|
865
|
+
return autoIncrement
|
|
866
|
+
? `${this.escapeId(columnName)} INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
867
|
+
: `${this.escapeId(columnName)} INTEGER PRIMARY KEY`;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Create column with specific affinity
|
|
871
|
+
*/
|
|
872
|
+
createColumnWithAffinity(columnName, affinity) {
|
|
873
|
+
return `${this.escapeId(columnName)} ${affinity}`;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Get column affinity
|
|
877
|
+
*/
|
|
878
|
+
getColumnAffinity(type) {
|
|
879
|
+
const upperType = type.toUpperCase();
|
|
880
|
+
if (upperType.includes('INT'))
|
|
881
|
+
return 'INTEGER';
|
|
882
|
+
if (upperType.includes('REAL') || upperType.includes('DOUBLE') || upperType.includes('FLOAT'))
|
|
883
|
+
return 'REAL';
|
|
884
|
+
if (upperType.includes('BLOB'))
|
|
885
|
+
return 'BLOB';
|
|
886
|
+
if (upperType.includes('CHAR') || upperType.includes('CLOB') || upperType.includes('TEXT'))
|
|
887
|
+
return 'TEXT';
|
|
888
|
+
return 'NUMERIC';
|
|
889
|
+
}
|
|
890
|
+
// ============================================
|
|
891
|
+
// CONSTRAINTS WITH ON CONFLICT
|
|
892
|
+
// ============================================
|
|
893
|
+
/**
|
|
894
|
+
* Create CHECK constraint
|
|
895
|
+
*/
|
|
896
|
+
createCheckConstraint(columnName, condition) {
|
|
897
|
+
return `CHECK (${columnName} ${condition})`;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Create table with ON CONFLICT clause
|
|
901
|
+
*/
|
|
902
|
+
createTableWithOnConflict(tableName, columns, conflictAlgorithm) {
|
|
903
|
+
const columnDefs = columns
|
|
904
|
+
.map((col) => {
|
|
905
|
+
let def = `${this.escapeId(col.name)} ${col.type}`;
|
|
906
|
+
if (col.constraints?.length) {
|
|
907
|
+
def += ` ${col.constraints.join(', ')}`;
|
|
908
|
+
}
|
|
909
|
+
return def;
|
|
910
|
+
})
|
|
911
|
+
.join(', ');
|
|
912
|
+
let sql = `CREATE TABLE ${this.escapeId(tableName)} (${columnDefs})`;
|
|
913
|
+
if (conflictAlgorithm) {
|
|
914
|
+
sql += ` ON CONFLICT ${conflictAlgorithm}`;
|
|
915
|
+
}
|
|
916
|
+
return sql;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Add ON CONFLICT to column definition
|
|
920
|
+
*/
|
|
921
|
+
addOnConflictClause(constraint, conflictAlgorithm) {
|
|
922
|
+
return `${constraint} ON CONFLICT ${conflictAlgorithm}`;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Create UNIQUE constraint
|
|
926
|
+
*/
|
|
927
|
+
createUniqueConstraint(columns) {
|
|
928
|
+
return `UNIQUE (${columns.map((c) => this.escapeId(c)).join(', ')})`;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Create NOT NULL constraint with ON CONFLICT
|
|
932
|
+
*/
|
|
933
|
+
createNotNullConstraint(columnName, conflictAlgorithm) {
|
|
934
|
+
return conflictAlgorithm ? `NOT NULL ON CONFLICT ${conflictAlgorithm}` : 'NOT NULL';
|
|
935
|
+
}
|
|
936
|
+
// ============================================
|
|
937
|
+
// PRAGMA COMMANDS
|
|
938
|
+
// ============================================
|
|
939
|
+
/**
|
|
940
|
+
* Execute a PRAGMA command
|
|
941
|
+
*/
|
|
942
|
+
async pragma(pragmaName, value) {
|
|
943
|
+
const sql = value !== undefined ? `PRAGMA ${pragmaName} = ${value}` : `PRAGMA ${pragmaName}`;
|
|
944
|
+
return this.dialect.query(sql);
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Get database info
|
|
948
|
+
*/
|
|
949
|
+
async getDatabaseInfo() {
|
|
950
|
+
const result = await Promise.all([
|
|
951
|
+
this.pragma('schema_version'),
|
|
952
|
+
this.pragma('user_version'),
|
|
953
|
+
this.pragma('foreign_keys'),
|
|
954
|
+
this.pragma('journal_mode'),
|
|
955
|
+
this.pragma('synchronous'),
|
|
956
|
+
this.pragma('cache_size'),
|
|
957
|
+
this.pragma('page_size'),
|
|
958
|
+
]);
|
|
959
|
+
return {
|
|
960
|
+
schemaVersion: result[0].rows[0]?.schema_version,
|
|
961
|
+
userVersion: result[1].rows[0]?.user_version,
|
|
962
|
+
foreignKeys: result[2].rows[0]?.foreign_keys === 1,
|
|
963
|
+
journalingMode: result[3].rows[0]?.journal_mode,
|
|
964
|
+
synchronous: result[4].rows[0]?.synchronous,
|
|
965
|
+
cacheSize: result[5].rows[0]?.cache_size,
|
|
966
|
+
pageSize: result[6].rows[0]?.page_size,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Set database options
|
|
971
|
+
*/
|
|
972
|
+
async setDatabaseOptions(options) {
|
|
973
|
+
if (options.foreignKeys !== undefined) {
|
|
974
|
+
await this.pragma('foreign_keys', options.foreignKeys ? 1 : 0);
|
|
975
|
+
}
|
|
976
|
+
if (options.journalingMode) {
|
|
977
|
+
await this.pragma('journal_mode', options.journalingMode);
|
|
978
|
+
}
|
|
979
|
+
if (options.synchronous) {
|
|
980
|
+
await this.pragma('synchronous', options.synchronous);
|
|
981
|
+
}
|
|
982
|
+
if (options.cacheSize !== undefined) {
|
|
983
|
+
await this.pragma('cache_size', options.cacheSize);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Get table info using PRAGMA
|
|
988
|
+
*/
|
|
989
|
+
async getTableInfo(tableName) {
|
|
990
|
+
// Note: PRAGMA table_info(x) takes its argument in parentheses, not as
|
|
991
|
+
// part of the pragma name - this.pragma() only builds `PRAGMA name` /
|
|
992
|
+
// `PRAGMA name = value`, so it's built directly here instead.
|
|
993
|
+
return this.dialect.query(`PRAGMA table_info(${this.escapeId(tableName)})`);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Get index list for table
|
|
997
|
+
*/
|
|
998
|
+
async getIndexList(tableName) {
|
|
999
|
+
return this.dialect.query(`PRAGMA index_list(${this.escapeId(tableName)})`);
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Get foreign key list for table
|
|
1003
|
+
*/
|
|
1004
|
+
async getForeignKeyList(tableName) {
|
|
1005
|
+
return this.dialect.query(`PRAGMA foreign_key_list(${this.escapeId(tableName)})`);
|
|
1006
|
+
}
|
|
1007
|
+
// ============================================
|
|
1008
|
+
// UTILITY METHODS
|
|
1009
|
+
// ============================================
|
|
1010
|
+
/**
|
|
1011
|
+
* Escape identifier
|
|
1012
|
+
*/
|
|
1013
|
+
escapeId(identifier) {
|
|
1014
|
+
const id = String(identifier ?? '');
|
|
1015
|
+
return `"${id.replace(/"/g, '""')}"`;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Escape a JSON path (or any other value) for safe interpolation inside a
|
|
1019
|
+
* single-quoted SQL string literal, by doubling embedded single quotes.
|
|
1020
|
+
*/
|
|
1021
|
+
escapePathLiteral(value) {
|
|
1022
|
+
return value.replace(/'/g, "''");
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Escape value
|
|
1026
|
+
*/
|
|
1027
|
+
escape(value) {
|
|
1028
|
+
if (value === null)
|
|
1029
|
+
return 'NULL';
|
|
1030
|
+
if (typeof value === 'number')
|
|
1031
|
+
return String(value);
|
|
1032
|
+
if (typeof value === 'boolean')
|
|
1033
|
+
return value ? '1' : '0';
|
|
1034
|
+
if (typeof value === 'string')
|
|
1035
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1036
|
+
if (value instanceof Date)
|
|
1037
|
+
return `'${value.toISOString()}'`;
|
|
1038
|
+
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
exports.SQLiteAdvanced = SQLiteAdvanced;
|
|
1042
|
+
/**
|
|
1043
|
+
* Create SQLiteAdvanced instance from dialect
|
|
1044
|
+
*/
|
|
1045
|
+
function createSQLiteAdvanced(dialect) {
|
|
1046
|
+
return new SQLiteAdvanced(dialect);
|
|
1047
|
+
}
|