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.
Files changed (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
@@ -0,0 +1,1532 @@
1
+ "use strict";
2
+ /**
3
+ * WHERE clause builder for the ORM
4
+ * Handles complex WHERE conditions with all supported operators
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.WhereBuilder = void 0;
8
+ exports.createWhereBuilder = createWhereBuilder;
9
+ exports.buildWhereClause = buildWhereClause;
10
+ // Import Op from operators for Symbol comparison
11
+ const operators_1 = require("../operators");
12
+ /**
13
+ * Check if a value is a column reference ({ $col: 'column' })
14
+ */
15
+ function isColumnReference(value) {
16
+ return (value !== null &&
17
+ typeof value === 'object' &&
18
+ '$col' in value &&
19
+ typeof value.$col === 'string');
20
+ }
21
+ /**
22
+ * Check if a key is Op.not Symbol
23
+ */
24
+ function isOpNot(key) {
25
+ return key === operators_1.Op.not;
26
+ }
27
+ /**
28
+ * Check if a value is a literal SQL ({ $literal: 'SQL' })
29
+ */
30
+ function isLiteralValue(value) {
31
+ return (value !== null &&
32
+ typeof value === 'object' &&
33
+ '$literal' in value &&
34
+ typeof value.$literal === 'string');
35
+ }
36
+ /**
37
+ * Convert a value to SQL string if it's a special type ($col, $literal)
38
+ * Returns the SQL string representation or null if it's a regular value
39
+ */
40
+ function valueToSql(value, escapeId) {
41
+ if (isColumnReference(value)) {
42
+ return escapeId(value.$col);
43
+ }
44
+ if (isLiteralValue(value)) {
45
+ return value.$literal;
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * WhereBuilder class for building WHERE clauses
51
+ */
52
+ class WhereBuilder {
53
+ constructor(options = {}) {
54
+ this.options = options;
55
+ }
56
+ /**
57
+ * Build a WHERE clause from WhereOptions
58
+ */
59
+ build(where) {
60
+ const values = [];
61
+ if (!where || Object.keys(where).length === 0) {
62
+ return { sql: '', values };
63
+ }
64
+ const result = this.buildCondition(where, values);
65
+ return { sql: result, values };
66
+ }
67
+ /**
68
+ * Build a condition (can be nested)
69
+ */
70
+ buildCondition(condition, values) {
71
+ if (!condition) {
72
+ return '';
73
+ }
74
+ // Handle primitive values (direct equality)
75
+ if (typeof condition !== 'object') {
76
+ values.push(condition);
77
+ return '?';
78
+ }
79
+ // Handle a bare raw-SQL literal used as a whole condition, e.g. a
80
+ // standalone full-text-search predicate: `where: literal('MATCH(...) AGAINST(...)')`,
81
+ // `Op.and: [literal('...'), { ... }]`. Without this, `{ $literal }` falls
82
+ // through to buildFieldConditions() and is silently misread as a field
83
+ // named "$literal" needing an operator, dropping the condition entirely.
84
+ if (isLiteralValue(condition)) {
85
+ return condition.$literal;
86
+ }
87
+ const cond = condition;
88
+ // Handle [Op.not]: { condition } - top-level negation
89
+ // Check if any key in the object is the Op.not Symbol
90
+ if (this.hasOpNotKey(cond)) {
91
+ return this.buildOpNotCondition(cond, values);
92
+ }
93
+ // Handle Symbol-based logical operators (Op.and, Op.or, Op.not)
94
+ if (this.hasLogicalOperatorSymbols(cond)) {
95
+ return this.buildLogicalCondition(cond, values);
96
+ }
97
+ // Handle string-based logical operators: $and, $or, $not
98
+ if (this.isLogicalOperator(cond)) {
99
+ return this.buildLogicalCondition(cond, values);
100
+ }
101
+ // Handle $where - explicit where clause with column reference
102
+ if ('$where' in cond) {
103
+ return this.buildWhereCondition(cond.$where, values);
104
+ }
105
+ // Handle field conditions
106
+ return this.buildFieldConditions(cond, values);
107
+ }
108
+ /**
109
+ * Check if the object has Op.not as a key (can be string or Symbol)
110
+ */
111
+ hasOpNotKey(obj) {
112
+ // Check string keys (for backward compatibility with 'not' string)
113
+ if (isOpNot('not')) {
114
+ if ('not' in obj) {
115
+ return true;
116
+ }
117
+ }
118
+ // Check Symbol keys (for Op.not Symbol)
119
+ const symbols = Object.getOwnPropertySymbols(obj);
120
+ return symbols.some((sym) => sym === operators_1.Op.not);
121
+ }
122
+ /**
123
+ * Build a condition with Op.not - { [Op.not]: { ... } } or { not: { ... } }
124
+ */
125
+ buildOpNotCondition(cond, values) {
126
+ // First check for Symbol key (Op.not)
127
+ const symbols = Object.getOwnPropertySymbols(cond);
128
+ for (const sym of symbols) {
129
+ if (sym === operators_1.Op.not) {
130
+ const notCondition = cond[sym];
131
+ const result = this.buildCondition(notCondition, values);
132
+ if (result) {
133
+ return `NOT (${result})`;
134
+ }
135
+ }
136
+ }
137
+ // Also check for string 'not' key for backward compatibility
138
+ if ('not' in cond) {
139
+ const notCondition = cond['not'];
140
+ const result = this.buildCondition(notCondition, values);
141
+ if (result) {
142
+ return `NOT (${result})`;
143
+ }
144
+ }
145
+ return '';
146
+ }
147
+ /**
148
+ * Build a $where condition with column reference
149
+ * { $col: 'user.id', $eq: 1 } => "user"."id" = ?
150
+ */
151
+ buildWhereCondition(whereClause, values) {
152
+ const col = whereClause.$col;
153
+ if (!col) {
154
+ return '';
155
+ }
156
+ // Get the operator and value (excluding $col)
157
+ const operatorEntries = Object.entries(whereClause).filter(([k]) => k !== '$col');
158
+ if (operatorEntries.length === 0) {
159
+ // Just column reference, no operator - treat as equality with no value
160
+ return this.escapeId(col);
161
+ }
162
+ // Build the condition for each operator
163
+ const fieldConditions = [];
164
+ for (const [opKey, opValue] of operatorEntries) {
165
+ const result = this.buildSingleWhereOperator(col, opKey, opValue, values);
166
+ if (result) {
167
+ fieldConditions.push(result);
168
+ }
169
+ }
170
+ return fieldConditions.join(' AND ');
171
+ }
172
+ /**
173
+ * Build a single where operator condition
174
+ * Handles special value types like $col (column references) and $literal (raw SQL)
175
+ */
176
+ buildSingleWhereOperator(field, operator, value, values) {
177
+ // Check if value is a special type ($col or $literal)
178
+ const sqlValue = valueToSql(value, this.escapeId.bind(this));
179
+ switch (operator) {
180
+ case '$eq':
181
+ if (sqlValue !== null) {
182
+ return `${this.escapeId(field)} = ${sqlValue}`;
183
+ }
184
+ values.push(value);
185
+ return `${this.escapeId(field)} = ?`;
186
+ case '$ne':
187
+ if (sqlValue !== null) {
188
+ return `${this.escapeId(field)} != ${sqlValue}`;
189
+ }
190
+ values.push(value);
191
+ return `${this.escapeId(field)} != ?`;
192
+ case '$gt':
193
+ if (sqlValue !== null) {
194
+ return `${this.escapeId(field)} > ${sqlValue}`;
195
+ }
196
+ values.push(value);
197
+ return `${this.escapeId(field)} > ?`;
198
+ case '$gte':
199
+ if (sqlValue !== null) {
200
+ return `${this.escapeId(field)} >= ${sqlValue}`;
201
+ }
202
+ values.push(value);
203
+ return `${this.escapeId(field)} >= ?`;
204
+ case '$lt':
205
+ if (sqlValue !== null) {
206
+ return `${this.escapeId(field)} < ${sqlValue}`;
207
+ }
208
+ values.push(value);
209
+ return `${this.escapeId(field)} < ?`;
210
+ case '$lte':
211
+ if (sqlValue !== null) {
212
+ return `${this.escapeId(field)} <= ${sqlValue}`;
213
+ }
214
+ values.push(value);
215
+ return `${this.escapeId(field)} <= ?`;
216
+ case '$like':
217
+ if (sqlValue !== null) {
218
+ return `${this.escapeId(field)} LIKE ${sqlValue}`;
219
+ }
220
+ values.push(value);
221
+ return `${this.escapeId(field)} LIKE ?`;
222
+ case '$notLike':
223
+ if (sqlValue !== null) {
224
+ return `${this.escapeId(field)} NOT LIKE ${sqlValue}`;
225
+ }
226
+ values.push(value);
227
+ return `${this.escapeId(field)} NOT LIKE ?`;
228
+ case '$iLike':
229
+ if (sqlValue !== null) {
230
+ return `${this.escapeId(field)} ILIKE ${sqlValue}`;
231
+ }
232
+ values.push(value);
233
+ return `${this.escapeId(field)} ILIKE ?`;
234
+ case '$notILike':
235
+ if (sqlValue !== null) {
236
+ return `${this.escapeId(field)} NOT ILIKE ${sqlValue}`;
237
+ }
238
+ values.push(value);
239
+ return `${this.escapeId(field)} NOT ILIKE ?`;
240
+ case '$in':
241
+ if (Array.isArray(value)) {
242
+ // Handle special values in array
243
+ const processedValues = [];
244
+ const regularValues = [];
245
+ value.forEach((v) => {
246
+ const sv = valueToSql(v, this.escapeId.bind(this));
247
+ if (sv !== null) {
248
+ processedValues.push(sv);
249
+ }
250
+ else {
251
+ regularValues.push(v);
252
+ }
253
+ });
254
+ const placeholders = [...processedValues, ...regularValues.map(() => '?')].join(', ');
255
+ values.push(...regularValues);
256
+ return `${this.escapeId(field)} IN (${placeholders})`;
257
+ }
258
+ return '';
259
+ case '$notIn':
260
+ if (Array.isArray(value)) {
261
+ // Handle special values in array
262
+ const processedValues = [];
263
+ const regularValues = [];
264
+ value.forEach((v) => {
265
+ const sv = valueToSql(v, this.escapeId.bind(this));
266
+ if (sv !== null) {
267
+ processedValues.push(sv);
268
+ }
269
+ else {
270
+ regularValues.push(v);
271
+ }
272
+ });
273
+ const placeholders = [...processedValues, ...regularValues.map(() => '?')].join(', ');
274
+ values.push(...regularValues);
275
+ return `${this.escapeId(field)} NOT IN (${placeholders})`;
276
+ }
277
+ return '';
278
+ case '$between':
279
+ if (Array.isArray(value) && value.length === 2) {
280
+ const startSql = valueToSql(value[0], this.escapeId.bind(this));
281
+ const endSql = valueToSql(value[1], this.escapeId.bind(this));
282
+ if (startSql !== null && endSql !== null) {
283
+ return `${this.escapeId(field)} BETWEEN ${startSql} AND ${endSql}`;
284
+ }
285
+ if (startSql !== null) {
286
+ values.push(value[1]);
287
+ return `${this.escapeId(field)} BETWEEN ${startSql} AND ?`;
288
+ }
289
+ if (endSql !== null) {
290
+ values.push(value[0]);
291
+ return `${this.escapeId(field)} BETWEEN ? AND ${endSql}`;
292
+ }
293
+ values.push(value[0], value[1]);
294
+ return `${this.escapeId(field)} BETWEEN ? AND ?`;
295
+ }
296
+ return '';
297
+ case '$notBetween':
298
+ if (Array.isArray(value) && value.length === 2) {
299
+ const startSql = valueToSql(value[0], this.escapeId.bind(this));
300
+ const endSql = valueToSql(value[1], this.escapeId.bind(this));
301
+ if (startSql !== null && endSql !== null) {
302
+ return `${this.escapeId(field)} NOT BETWEEN ${startSql} AND ${endSql}`;
303
+ }
304
+ if (startSql !== null) {
305
+ values.push(value[1]);
306
+ return `${this.escapeId(field)} NOT BETWEEN ${startSql} AND ?`;
307
+ }
308
+ if (endSql !== null) {
309
+ values.push(value[0]);
310
+ return `${this.escapeId(field)} NOT BETWEEN ? AND ${endSql}`;
311
+ }
312
+ values.push(value[0], value[1]);
313
+ return `${this.escapeId(field)} NOT BETWEEN ? AND ?`;
314
+ }
315
+ return '';
316
+ case '$isNull':
317
+ return `${this.escapeId(field)} IS NULL`;
318
+ case '$isNotNull':
319
+ return `${this.escapeId(field)} IS NOT NULL`;
320
+ case '$col':
321
+ // Column reference: compare to another column
322
+ return `${this.escapeId(field)} = ${this.escapeId(value)}`;
323
+ default:
324
+ // Unknown operator
325
+ return '';
326
+ }
327
+ }
328
+ /**
329
+ * Check if the object has Op.and, Op.or, or Op.not as keys (Symbol-based logical operators)
330
+ */
331
+ hasLogicalOperatorSymbols(obj) {
332
+ const symbols = Object.getOwnPropertySymbols(obj);
333
+ return symbols.some((sym) => sym === operators_1.Op.and || sym === operators_1.Op.or || sym === operators_1.Op.not);
334
+ }
335
+ /**
336
+ * Check if the object is a logical operator container
337
+ */
338
+ isLogicalOperator(obj) {
339
+ // Check for string keys ($and, $or, $not)
340
+ const hasStringKeys = '$and' in obj || '$or' in obj || '$not' in obj;
341
+ if (hasStringKeys)
342
+ return true;
343
+ // Check for Symbol keys (Op.and, Op.or, Op.not)
344
+ return this.hasLogicalOperatorSymbols(obj);
345
+ }
346
+ /**
347
+ * Build logical conditions ($and, $or, $not) or Symbol-based (Op.and, Op.or, Op.not)
348
+ */
349
+ buildLogicalCondition(cond, values) {
350
+ const conditions = [];
351
+ // Handle $and (string key)
352
+ if (cond.$and) {
353
+ const andConditions = cond.$and
354
+ .map((c) => {
355
+ const result = this.buildCondition(c, values);
356
+ return result ? `(${result})` : '';
357
+ })
358
+ .filter((s) => s);
359
+ if (andConditions.length > 0) {
360
+ conditions.push(andConditions.join(' AND '));
361
+ }
362
+ }
363
+ // Handle Op.and (Symbol key)
364
+ const symbolKeys = Object.getOwnPropertySymbols(cond);
365
+ for (const sym of symbolKeys) {
366
+ if (sym === operators_1.Op.and) {
367
+ const andValue = cond[sym];
368
+ const andConditions = andValue
369
+ .map((c) => {
370
+ const result = this.buildCondition(c, values);
371
+ return result ? `(${result})` : '';
372
+ })
373
+ .filter((s) => s);
374
+ if (andConditions.length > 0) {
375
+ conditions.push(andConditions.join(' AND '));
376
+ }
377
+ }
378
+ }
379
+ // Handle $or (string key)
380
+ if (cond.$or) {
381
+ const orConditions = cond.$or
382
+ .map((c) => {
383
+ const result = this.buildCondition(c, values);
384
+ return result ? `(${result})` : '';
385
+ })
386
+ .filter((s) => s);
387
+ if (orConditions.length > 0) {
388
+ conditions.push(orConditions.join(' OR '));
389
+ }
390
+ }
391
+ // Handle Op.or (Symbol key)
392
+ for (const sym of symbolKeys) {
393
+ if (sym === operators_1.Op.or) {
394
+ const orValue = cond[sym];
395
+ const orConditions = orValue
396
+ .map((c) => {
397
+ const result = this.buildCondition(c, values);
398
+ return result ? `(${result})` : '';
399
+ })
400
+ .filter((s) => s);
401
+ if (orConditions.length > 0) {
402
+ conditions.push(orConditions.join(' OR '));
403
+ }
404
+ }
405
+ }
406
+ // Handle $not (string key)
407
+ if (cond.$not) {
408
+ const result = this.buildCondition(cond.$not, values);
409
+ if (result) {
410
+ conditions.push(`NOT (${result})`);
411
+ }
412
+ }
413
+ // Handle Op.not (Symbol key)
414
+ for (const sym of symbolKeys) {
415
+ if (sym === operators_1.Op.not) {
416
+ const notValue = cond[sym];
417
+ const result = this.buildCondition(notValue, values);
418
+ if (result) {
419
+ conditions.push(`NOT (${result})`);
420
+ }
421
+ }
422
+ }
423
+ return conditions.join(' AND ');
424
+ }
425
+ /**
426
+ * Check if a key is a JSON path Symbol created by Op.json
427
+ * JSON path symbols have descriptions like "json:$.path" or "json:path"
428
+ */
429
+ isJsonPathSymbol(key) {
430
+ if (typeof key === 'symbol') {
431
+ const desc = key.description || '';
432
+ return desc.startsWith('json:');
433
+ }
434
+ return false;
435
+ }
436
+ /**
437
+ * Extract the JSON path from a Symbol created by Op.json
438
+ * The path is stored in the Symbol's description after "json:"
439
+ */
440
+ extractJsonPath(key) {
441
+ const desc = key.description || '';
442
+ // Remove the "json:" prefix to get the path
443
+ let path = desc.substring(5);
444
+ // Ensure path starts with $.
445
+ if (!path.startsWith('$.')) {
446
+ path = `$.${path}`;
447
+ }
448
+ return path;
449
+ }
450
+ /**
451
+ * Build field-level conditions
452
+ */
453
+ buildFieldConditions(cond, values) {
454
+ const fieldConditions = [];
455
+ // First, handle string keys from Object.entries
456
+ for (const [key, value] of Object.entries(cond)) {
457
+ // Skip logical operators
458
+ if (key.startsWith('$')) {
459
+ continue;
460
+ }
461
+ if (value === null) {
462
+ fieldConditions.push(`${this.escapeId(key)} IS NULL`);
463
+ }
464
+ else if (typeof value === 'object') {
465
+ const result = this.buildOperatorCondition(key, value, values);
466
+ if (result) {
467
+ fieldConditions.push(result);
468
+ }
469
+ }
470
+ else {
471
+ values.push(value);
472
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
473
+ }
474
+ }
475
+ // Also handle Symbol keys (e.g., Op.json('path') returns a Symbol used as computed property key)
476
+ const symbolKeys = Object.getOwnPropertySymbols(cond);
477
+ for (const sym of symbolKeys) {
478
+ const value = cond[sym];
479
+ // Check if it's a JSON path Symbol
480
+ if (this.isJsonPathSymbol(sym)) {
481
+ const path = this.extractJsonPath(sym);
482
+ // Parse the path - format is "field.path" or "$.field.path"
483
+ // We need to extract the column name and JSON path
484
+ let field;
485
+ let jsonPath;
486
+ if (path.startsWith('$.')) {
487
+ // Format: $.field.subfield
488
+ const withoutDollar = path.substring(2);
489
+ const dotIndex = withoutDollar.indexOf('.');
490
+ if (dotIndex === -1) {
491
+ field = withoutDollar;
492
+ jsonPath = '';
493
+ }
494
+ else {
495
+ field = withoutDollar.substring(0, dotIndex);
496
+ jsonPath = withoutDollar.substring(dotIndex + 1);
497
+ }
498
+ }
499
+ else {
500
+ // Format: field.subfield
501
+ const dotIndex = path.indexOf('.');
502
+ if (dotIndex === -1) {
503
+ field = path;
504
+ jsonPath = '';
505
+ }
506
+ else {
507
+ field = path.substring(0, dotIndex);
508
+ jsonPath = path.substring(dotIndex + 1);
509
+ }
510
+ }
511
+ // Build the JSON extraction SQL
512
+ const jsonExtract = jsonPath
513
+ ? `json_extract(${this.escapeId(field)}, '$.${jsonPath}')`
514
+ : this.escapeId(field);
515
+ if (value === null) {
516
+ fieldConditions.push(`${jsonExtract} IS NULL`);
517
+ }
518
+ else if (typeof value === 'object') {
519
+ // If value is an object with operators (e.g., { $gt: 5 })
520
+ // We need to pass the JSON extraction as the field name
521
+ const result = this.buildOperatorCondition(jsonExtract, value, values);
522
+ if (result) {
523
+ fieldConditions.push(result);
524
+ }
525
+ }
526
+ else {
527
+ // Simple comparison
528
+ values.push(value);
529
+ fieldConditions.push(`${jsonExtract} = ?`);
530
+ }
531
+ }
532
+ }
533
+ return fieldConditions.join(' AND ');
534
+ }
535
+ /**
536
+ * Build condition for a specific operator
537
+ */
538
+ buildOperatorCondition(field, operator, values) {
539
+ // Helper to handle special values ($col, $literal)
540
+ const handleValue = (value) => {
541
+ return valueToSql(value, this.escapeId.bind(this));
542
+ };
543
+ // Handle Op.not Symbol as key: { [Op.not]: 'active' } => field != 'active'
544
+ const symbolKeys = Object.getOwnPropertySymbols(operator);
545
+ for (const sym of symbolKeys) {
546
+ if (sym === operators_1.Op.not) {
547
+ const notValue = operator[sym];
548
+ const sqlValue = handleValue(notValue);
549
+ if (sqlValue !== null) {
550
+ return `${this.escapeId(field)} != ${sqlValue}`;
551
+ }
552
+ values.push(notValue);
553
+ return `${this.escapeId(field)} != ?`;
554
+ }
555
+ }
556
+ // Also handle 'not' as string key for backward compatibility
557
+ if ('not' in operator && !operator.$not) {
558
+ const notValue = operator.not;
559
+ const sqlValue = handleValue(notValue);
560
+ if (sqlValue !== null) {
561
+ return `${this.escapeId(field)} != ${sqlValue}`;
562
+ }
563
+ values.push(notValue);
564
+ return `${this.escapeId(field)} != ?`;
565
+ }
566
+ // $eq - equality
567
+ if (operator.$eq !== undefined) {
568
+ const sqlValue = handleValue(operator.$eq);
569
+ if (sqlValue !== null) {
570
+ return `${this.escapeId(field)} = ${sqlValue}`;
571
+ }
572
+ values.push(operator.$eq);
573
+ return `${this.escapeId(field)} = ?`;
574
+ }
575
+ // $ne - not equal
576
+ if (operator.$ne !== undefined) {
577
+ const sqlValue = handleValue(operator.$ne);
578
+ if (sqlValue !== null) {
579
+ return `${this.escapeId(field)} != ${sqlValue}`;
580
+ }
581
+ values.push(operator.$ne);
582
+ return `${this.escapeId(field)} != ?`;
583
+ }
584
+ // $gt - greater than
585
+ if (operator.$gt !== undefined) {
586
+ const sqlValue = handleValue(operator.$gt);
587
+ if (sqlValue !== null) {
588
+ return `${this.escapeId(field)} > ${sqlValue}`;
589
+ }
590
+ values.push(operator.$gt);
591
+ return `${this.escapeId(field)} > ?`;
592
+ }
593
+ // $gte - greater than or equal
594
+ if (operator.$gte !== undefined) {
595
+ const sqlValue = handleValue(operator.$gte);
596
+ if (sqlValue !== null) {
597
+ return `${this.escapeId(field)} >= ${sqlValue}`;
598
+ }
599
+ values.push(operator.$gte);
600
+ return `${this.escapeId(field)} >= ?`;
601
+ }
602
+ // $lt - less than
603
+ if (operator.$lt !== undefined) {
604
+ const sqlValue = handleValue(operator.$lt);
605
+ if (sqlValue !== null) {
606
+ return `${this.escapeId(field)} < ${sqlValue}`;
607
+ }
608
+ values.push(operator.$lt);
609
+ return `${this.escapeId(field)} < ?`;
610
+ }
611
+ // $lte - less than or equal
612
+ if (operator.$lte !== undefined) {
613
+ const sqlValue = handleValue(operator.$lte);
614
+ if (sqlValue !== null) {
615
+ return `${this.escapeId(field)} <= ${sqlValue}`;
616
+ }
617
+ values.push(operator.$lte);
618
+ return `${this.escapeId(field)} <= ?`;
619
+ }
620
+ // $like - LIKE operator
621
+ if (operator.$like !== undefined) {
622
+ const sqlValue = handleValue(operator.$like);
623
+ if (sqlValue !== null) {
624
+ return `${this.escapeId(field)} LIKE ${sqlValue}`;
625
+ }
626
+ values.push(operator.$like);
627
+ return `${this.escapeId(field)} LIKE ?`;
628
+ }
629
+ // $notLike - NOT LIKE operator
630
+ if (operator.$notLike !== undefined) {
631
+ const sqlValue = handleValue(operator.$notLike);
632
+ if (sqlValue !== null) {
633
+ return `${this.escapeId(field)} NOT LIKE ${sqlValue}`;
634
+ }
635
+ values.push(operator.$notLike);
636
+ return `${this.escapeId(field)} NOT LIKE ?`;
637
+ }
638
+ // $in - IN operator
639
+ if (operator.$in !== undefined) {
640
+ const inValues = operator.$in;
641
+ // Handle subquery (literal SQL string starting with SELECT)
642
+ if (typeof inValues === 'string' && inValues.toUpperCase().startsWith('SELECT')) {
643
+ return `${this.escapeId(field)} IN (${inValues})`;
644
+ }
645
+ // Handle literal SQL subquery object
646
+ if (typeof inValues === 'object' && inValues !== null && '$literal' in inValues) {
647
+ return `${this.escapeId(field)} IN (${inValues.$literal})`;
648
+ }
649
+ if (!Array.isArray(inValues) || inValues.length === 0) {
650
+ return '1=0'; // Always false for empty IN
651
+ }
652
+ // Handle array of special values
653
+ const placeholders = [];
654
+ for (const item of inValues) {
655
+ const itemSql = handleValue(item);
656
+ if (itemSql !== null) {
657
+ placeholders.push(itemSql);
658
+ }
659
+ else {
660
+ placeholders.push('?');
661
+ values.push(item);
662
+ }
663
+ }
664
+ return `${this.escapeId(field)} IN (${placeholders.join(', ')})`;
665
+ }
666
+ // $notIn - NOT IN operator
667
+ if (operator.$notIn !== undefined) {
668
+ const notInValues = operator.$notIn;
669
+ // Handle subquery (literal SQL string starting with SELECT)
670
+ if (typeof notInValues === 'string' &&
671
+ notInValues.toUpperCase().startsWith('SELECT')) {
672
+ return `${this.escapeId(field)} NOT IN (${notInValues})`;
673
+ }
674
+ // Handle literal SQL subquery object
675
+ if (typeof notInValues === 'object' &&
676
+ notInValues !== null &&
677
+ '$literal' in notInValues) {
678
+ return `${this.escapeId(field)} NOT IN (${notInValues.$literal})`;
679
+ }
680
+ if (!Array.isArray(notInValues) || notInValues.length === 0) {
681
+ return '1=1'; // Always true for empty NOT IN
682
+ }
683
+ // Handle array of special values
684
+ const placeholders = [];
685
+ for (const item of notInValues) {
686
+ const itemSql = handleValue(item);
687
+ if (itemSql !== null) {
688
+ placeholders.push(itemSql);
689
+ }
690
+ else {
691
+ placeholders.push('?');
692
+ values.push(item);
693
+ }
694
+ }
695
+ return `${this.escapeId(field)} NOT IN (${placeholders.join(', ')})`;
696
+ }
697
+ // $between - BETWEEN operator (inclusive)
698
+ if (operator.$between !== undefined) {
699
+ const between = operator.$between;
700
+ const startSql = handleValue(between[0]);
701
+ const endSql = handleValue(between[1]);
702
+ if (startSql !== null && endSql !== null) {
703
+ return `${this.escapeId(field)} BETWEEN ${startSql} AND ${endSql}`;
704
+ }
705
+ if (startSql !== null) {
706
+ values.push(between[1]);
707
+ return `${this.escapeId(field)} BETWEEN ${startSql} AND ?`;
708
+ }
709
+ if (endSql !== null) {
710
+ values.push(between[0]);
711
+ return `${this.escapeId(field)} BETWEEN ? AND ${endSql}`;
712
+ }
713
+ values.push(between[0], between[1]);
714
+ return `${this.escapeId(field)} BETWEEN ? AND ?`;
715
+ }
716
+ // $exists - EXISTS subquery operator
717
+ if (operator.$exists !== undefined) {
718
+ const subquery = operator.$exists;
719
+ // Handle string subquery
720
+ if (typeof subquery === 'string') {
721
+ return `EXISTS (${subquery})`;
722
+ }
723
+ // Handle literal SQL subquery
724
+ if (typeof subquery === 'object' && subquery !== null && '$literal' in subquery) {
725
+ return `EXISTS (${subquery.$literal})`;
726
+ }
727
+ return '1=0'; // Default false if not a valid subquery
728
+ }
729
+ // $notExists - NOT EXISTS subquery operator
730
+ if (operator.$notExists !== undefined) {
731
+ const subquery = operator.$notExists;
732
+ // Handle string subquery
733
+ if (typeof subquery === 'string') {
734
+ return `NOT EXISTS (${subquery})`;
735
+ }
736
+ // Handle literal SQL subquery
737
+ if (typeof subquery === 'object' && subquery !== null && '$literal' in subquery) {
738
+ return `NOT EXISTS (${subquery.$literal})`;
739
+ }
740
+ return '1=1'; // Default true if not a valid subquery
741
+ }
742
+ // $notBetween - NOT BETWEEN operator
743
+ if (operator.$notBetween !== undefined) {
744
+ const notBetween = operator.$notBetween;
745
+ const startSql = handleValue(notBetween[0]);
746
+ const endSql = handleValue(notBetween[1]);
747
+ if (startSql !== null && endSql !== null) {
748
+ return `${this.escapeId(field)} NOT BETWEEN ${startSql} AND ${endSql}`;
749
+ }
750
+ if (startSql !== null) {
751
+ values.push(notBetween[1]);
752
+ return `${this.escapeId(field)} NOT BETWEEN ${startSql} AND ?`;
753
+ }
754
+ if (endSql !== null) {
755
+ values.push(notBetween[0]);
756
+ return `${this.escapeId(field)} NOT BETWEEN ? AND ${endSql}`;
757
+ }
758
+ values.push(notBetween[0], notBetween[1]);
759
+ return `${this.escapeId(field)} NOT BETWEEN ? AND ?`;
760
+ }
761
+ // $isNull - IS NULL operator
762
+ if (operator.$isNull !== undefined) {
763
+ if (operator.$isNull) {
764
+ return `${this.escapeId(field)} IS NULL`;
765
+ }
766
+ else {
767
+ return `${this.escapeId(field)} IS NOT NULL`;
768
+ }
769
+ }
770
+ // $isNotNull - IS NOT NULL operator (shorthand)
771
+ if (operator.$isNotNull !== undefined) {
772
+ if (operator.$isNotNull) {
773
+ return `${this.escapeId(field)} IS NOT NULL`;
774
+ }
775
+ else {
776
+ return `${this.escapeId(field)} IS NULL`;
777
+ }
778
+ }
779
+ // $regexp - REGEXP operator
780
+ if (operator.$regexp !== undefined) {
781
+ const sqlValue = handleValue(operator.$regexp);
782
+ if (sqlValue !== null) {
783
+ return `${this.escapeId(field)} REGEXP ${sqlValue}`;
784
+ }
785
+ values.push(operator.$regexp);
786
+ return `${this.escapeId(field)} REGEXP ?`;
787
+ }
788
+ // $notRegexp - NOT REGEXP operator
789
+ if (operator.$notRegexp !== undefined) {
790
+ const sqlValue = handleValue(operator.$notRegexp);
791
+ if (sqlValue !== null) {
792
+ return `${this.escapeId(field)} NOT REGEXP ${sqlValue}`;
793
+ }
794
+ values.push(operator.$notRegexp);
795
+ return `${this.escapeId(field)} NOT REGEXP ?`;
796
+ }
797
+ // $json - JSON column query operator
798
+ if (operator.$json !== undefined) {
799
+ const jsonValue = operator.$json;
800
+ if (typeof jsonValue === 'object' && jsonValue !== null) {
801
+ // Handle { path: 'key.subkey', value: 'something' }
802
+ if ('path' in jsonValue && 'value' in jsonValue) {
803
+ const path = jsonValue.path;
804
+ const normalizedPath = path.startsWith('$.') ? path : `$.${path}`;
805
+ values.push(jsonValue.value);
806
+ return `${this.buildJsonExtract(field, normalizedPath)} = ?`;
807
+ }
808
+ // Handle nested JSON query { key: value } - converts to JSON containment
809
+ values.push(JSON.stringify(jsonValue));
810
+ return `${this.escapeId(field)} = ?`;
811
+ }
812
+ // Handle plain value for JSON equality
813
+ values.push(JSON.stringify(jsonValue));
814
+ return `${this.escapeId(field)} = ?`;
815
+ }
816
+ // $jsonPath - JSON path query operator (Op.jsonPath('field', 'path'))
817
+ // Expected format: { $jsonPath: { field: 'column', path: 'key.subkey' } }
818
+ if (operator.$jsonPath !== undefined) {
819
+ const jsonPathValue = operator.$jsonPath;
820
+ if (typeof jsonPathValue === 'object' && jsonPathValue !== null && 'path' in jsonPathValue) {
821
+ const path = jsonPathValue.path;
822
+ const normalizedPath = path.startsWith('$.') ? path : `$.${path}`;
823
+ const compareValue = jsonPathValue.value;
824
+ if (compareValue !== undefined) {
825
+ // Value comparison: json_extract(field, '$.path') = ?
826
+ values.push(compareValue);
827
+ return `${this.buildJsonExtract(field, normalizedPath)} = ?`;
828
+ }
829
+ // No value - just check if path exists (IS NOT NULL)
830
+ return `${this.buildJsonExtract(field, normalizedPath)} IS NOT NULL`;
831
+ }
832
+ // Fallback
833
+ return '';
834
+ }
835
+ // Handle Symbol-based JSON operators (Op.json('path') creates Symbol.for('json:path'))
836
+ // These appear as keys starting with '$json:' in the operator object
837
+ for (const key of Object.keys(operator)) {
838
+ if (key.startsWith('$json:')) {
839
+ const jsonPath = key.substring(6); // Remove '$json:' prefix
840
+ const normalizedPath = jsonPath.startsWith('$.') ? jsonPath : `$.${jsonPath}`;
841
+ const compareValue = operator[key];
842
+ if (compareValue !== undefined) {
843
+ values.push(compareValue);
844
+ return `${this.buildJsonExtract(field, normalizedPath)} = ?`;
845
+ }
846
+ // No value - check if path exists
847
+ return `${this.buildJsonExtract(field, normalizedPath)} IS NOT NULL`;
848
+ }
849
+ }
850
+ // $contains - JSON contains operator (for objects/arrays)
851
+ // PostgreSQL: @>, MySQL: JSON_CONTAINS, SQLite: json_each
852
+ if (operator.$contains !== undefined) {
853
+ const containsValue = operator.$contains;
854
+ values.push(JSON.stringify(containsValue));
855
+ // Use JSON_EXTRACT for cross-database compatibility
856
+ // For exact match, we compare the full JSON
857
+ return `${this.escapeId(field)} = ?`;
858
+ }
859
+ // $containedBy - JSON contained by operator
860
+ // PostgreSQL: <@
861
+ if (operator.$containedBy !== undefined) {
862
+ const containedValue = operator.$containedBy;
863
+ values.push(JSON.stringify(containedValue));
864
+ // Cross-database: check if the column's JSON is contained by the value
865
+ return `${this.escapeId(field)} @> ?`;
866
+ }
867
+ // $overlap - JSON overlap (arrays) - PostgreSQL: &&
868
+ if (operator.$overlap !== undefined) {
869
+ const overlapValue = operator.$overlap;
870
+ values.push(JSON.stringify(overlapValue));
871
+ // Cross-database: check array overlap
872
+ return `${this.escapeId(field)} @> ?`;
873
+ }
874
+ // $arrayContains - PostgreSQL array contains operator (@>)
875
+ // Only supported in PostgreSQL
876
+ if (operator.$arrayContains !== undefined) {
877
+ const containsValue = operator.$arrayContains;
878
+ if (this.options.dialectName !== 'postgres' && this.options.dialectName !== 'postgresql') {
879
+ // Array operators only work in PostgreSQL
880
+ throw new Error('Array operators are only supported in PostgreSQL');
881
+ }
882
+ const arraySql = this.buildArrayLiteral(containsValue);
883
+ return `${this.escapeId(field)} @> ${arraySql}`;
884
+ }
885
+ // $arrayContainedBy - PostgreSQL array contained by operator (<@)
886
+ // Only supported in PostgreSQL
887
+ if (operator.$arrayContainedBy !== undefined) {
888
+ const containedValue = operator.$arrayContainedBy;
889
+ if (this.options.dialectName !== 'postgres' && this.options.dialectName !== 'postgresql') {
890
+ throw new Error('Array operators are only supported in PostgreSQL');
891
+ }
892
+ const arraySql = this.buildArrayLiteral(containedValue);
893
+ return `${this.escapeId(field)} <@ ${arraySql}`;
894
+ }
895
+ // $arrayOverlaps - PostgreSQL array overlaps operator (&&)
896
+ // Only supported in PostgreSQL
897
+ if (operator.$arrayOverlaps !== undefined) {
898
+ const overlapValue = operator.$arrayOverlaps;
899
+ if (this.options.dialectName !== 'postgres' && this.options.dialectName !== 'postgresql') {
900
+ throw new Error('Array operators are only supported in PostgreSQL');
901
+ }
902
+ const arraySql = this.buildArrayLiteral(overlapValue);
903
+ return `${this.escapeId(field)} && ${arraySql}`;
904
+ }
905
+ // $arrayAny - PostgreSQL array ANY operator
906
+ // Only supported in PostgreSQL
907
+ if (operator.$arrayAny !== undefined) {
908
+ const anyValue = operator.$arrayAny;
909
+ if (this.options.dialectName !== 'postgres' && this.options.dialectName !== 'postgresql') {
910
+ throw new Error('Array operators are only supported in PostgreSQL');
911
+ }
912
+ const escapedValue = this.options.dialect?.escape
913
+ ? this.options.dialect.escape(anyValue)
914
+ : this.escapeValue(anyValue);
915
+ return `${escapedValue} = ANY(${this.escapeId(field)})`;
916
+ }
917
+ // $arrayAll - PostgreSQL array ALL operator
918
+ // Only supported in PostgreSQL
919
+ if (operator.$arrayAll !== undefined) {
920
+ const allValue = operator.$arrayAll;
921
+ if (this.options.dialectName !== 'postgres' && this.options.dialectName !== 'postgresql') {
922
+ throw new Error('Array operators are only supported in PostgreSQL');
923
+ }
924
+ // For array ALL, we compare: value > ALL(array_column)
925
+ // This checks if value is greater than all elements in the array
926
+ const escapedValue = this.options.dialect?.escape
927
+ ? this.options.dialect.escape(allValue)
928
+ : this.escapeValue(allValue);
929
+ // Use the comparison operator from the inner operator if provided
930
+ if (typeof allValue === 'object' && allValue !== null && '$gt' in allValue) {
931
+ const compValue = allValue.$gt;
932
+ const compEscaped = this.options.dialect?.escape
933
+ ? this.options.dialect.escape(compValue)
934
+ : this.escapeValue(compValue);
935
+ return `${compEscaped} > ALL(${this.escapeId(field)})`;
936
+ }
937
+ return `${escapedValue} = ALL(${this.escapeId(field)})`;
938
+ }
939
+ // $keyExists - Check if key exists in JSON
940
+ // PostgreSQL: ? (jsonb_exists), MySQL: JSON_CONTAINS_PATH, SQLite: json_each
941
+ if (operator.$keyExists !== undefined) {
942
+ const keyPath = operator.$keyExists;
943
+ // Use json_extract for SQLite/MySQL and ->> for PostgreSQL style comparison
944
+ // Check if the key path returns non-null
945
+ return `json_extract(${this.escapeId(field)}, '$.${keyPath}') IS NOT NULL`;
946
+ }
947
+ // $jsonContains - JSON contains operator (Op.jsonContains)
948
+ // PostgreSQL: @>, MySQL: JSON_CONTAINS, SQLite: json_extract
949
+ if (operator.$jsonContains !== undefined) {
950
+ const containsValue = operator.$jsonContains;
951
+ values.push(JSON.stringify(containsValue));
952
+ // Use dialect-specific JSON contains
953
+ // For cross-database compatibility, we use the default dialect approach
954
+ // PostgreSQL: @> operator
955
+ // MySQL: JSON_CONTAINS
956
+ // SQLite: json_each
957
+ if (this.options.dialectName === 'postgres') {
958
+ return `${this.escapeId(field)} @> ?`;
959
+ }
960
+ else if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
961
+ return `JSON_CONTAINS(${this.escapeId(field)}, ?)`;
962
+ }
963
+ else {
964
+ // SQLite and default: use JSON comparison
965
+ return `${this.escapeId(field)} = ?`;
966
+ }
967
+ }
968
+ // $jsonHasKey - JSON has key operator (Op.jsonHasKey)
969
+ // PostgreSQL: ? (jsonb_exists), MySQL: JSON_CONTAINS_PATH, SQLite: json_each
970
+ if (operator.$jsonHasKey !== undefined) {
971
+ const keyPath = String(operator.$jsonHasKey);
972
+ // Normalize the key path
973
+ const normalizedKey = keyPath.startsWith('$.') ? keyPath.substring(2) : keyPath;
974
+ if (this.options.dialectName === 'postgres') {
975
+ // PostgreSQL: ? operator for jsonb
976
+ return `${this.escapeId(field)} ? $1`;
977
+ }
978
+ else if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
979
+ // MySQL/MariaDB: JSON_CONTAINS_PATH(col, 'one', '$.key')
980
+ return `JSON_CONTAINS_PATH(${this.escapeId(field)}, 'one', '$.${normalizedKey}')`;
981
+ }
982
+ else {
983
+ // SQLite: json_extract returns non-null if key exists
984
+ return `json_extract(${this.escapeId(field)}, '$.${normalizedKey}') IS NOT NULL`;
985
+ }
986
+ }
987
+ // $jsonPath - JSON path query operator (Op.jsonPath)
988
+ // PostgreSQL: ->> and #>>, MySQL: JSON_EXTRACT, SQLite: json_extract
989
+ if (operator.$jsonPath !== undefined) {
990
+ const jsonPathValue = operator.$jsonPath;
991
+ if (typeof jsonPathValue === 'object' && jsonPathValue !== null && 'path' in jsonPathValue) {
992
+ const path = jsonPathValue.path;
993
+ const normalizedPath = path.startsWith('$.') ? path : `$.${path}`;
994
+ const compareValue = jsonPathValue.value;
995
+ if (compareValue !== undefined) {
996
+ // Value comparison: json_extract(field, '$.path') = ?
997
+ values.push(compareValue);
998
+ return `${this.buildJsonExtract(field, normalizedPath)} = ?`;
999
+ }
1000
+ // No value - just check if path exists (IS NOT NULL)
1001
+ return `${this.buildJsonExtract(field, normalizedPath)} IS NOT NULL`;
1002
+ }
1003
+ // Fallback
1004
+ return '';
1005
+ }
1006
+ // $jsonbExtract - JSONB extract field (PostgreSQL: ->)
1007
+ // Extracts a JSON object field, returns JSON
1008
+ if (operator.$jsonbExtract !== undefined) {
1009
+ const extractValue = operator.$jsonbExtract;
1010
+ if (typeof extractValue === 'object' && extractValue !== null && 'path' in extractValue) {
1011
+ const path = String(extractValue.path);
1012
+ const compareValue = extractValue.value;
1013
+ // PostgreSQL: field -> 'path'
1014
+ const columnRef = this.escapeId(field);
1015
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1016
+ if (compareValue !== undefined) {
1017
+ values.push(compareValue);
1018
+ return `${columnRef} -> '${path}' = ?`;
1019
+ }
1020
+ return `${columnRef} -> '${path}' IS NOT NULL`;
1021
+ }
1022
+ // Fallback for other dialects: use json_extract
1023
+ const pathStr = path.startsWith('$.') ? path : `$.${path}`;
1024
+ if (compareValue !== undefined) {
1025
+ values.push(compareValue);
1026
+ return `json_extract(${columnRef}, '${pathStr}') = ?`;
1027
+ }
1028
+ return `json_extract(${columnRef}, '${pathStr}') IS NOT NULL`;
1029
+ }
1030
+ return '';
1031
+ }
1032
+ // $jsonbExtractText - JSONB extract field as text (PostgreSQL: ->>)
1033
+ // Extracts a JSON object field as text, returns text
1034
+ if (operator.$jsonbExtractText !== undefined) {
1035
+ const extractValue = operator.$jsonbExtractText;
1036
+ if (typeof extractValue === 'object' && extractValue !== null && 'path' in extractValue) {
1037
+ const path = String(extractValue.path);
1038
+ const compareValue = extractValue.value;
1039
+ // PostgreSQL: field ->> 'path'
1040
+ const columnRef = this.escapeId(field);
1041
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1042
+ if (compareValue !== undefined) {
1043
+ values.push(compareValue);
1044
+ return `${columnRef} ->> '${path}' = ?`;
1045
+ }
1046
+ return `${columnRef} ->> '${path}' IS NOT NULL`;
1047
+ }
1048
+ // Fallback for other dialects: use json_extract
1049
+ const pathStr = path.startsWith('$.') ? path : `$.${path}`;
1050
+ if (compareValue !== undefined) {
1051
+ values.push(compareValue);
1052
+ return `json_extract(${columnRef}, '${pathStr}') = ?`;
1053
+ }
1054
+ return `json_extract(${columnRef}, '${pathStr}') IS NOT NULL`;
1055
+ }
1056
+ return '';
1057
+ }
1058
+ // $jsonbExtractPath - JSONB extract by path (PostgreSQL: #>)
1059
+ // Extracts JSON by path, returns JSON
1060
+ if (operator.$jsonbExtractPath !== undefined) {
1061
+ const extractValue = operator.$jsonbExtractPath;
1062
+ if (typeof extractValue === 'object' && extractValue !== null && 'path' in extractValue) {
1063
+ const pathValue = extractValue.path;
1064
+ const compareValue = extractValue.value;
1065
+ // Convert path to PostgreSQL format: ['a', 'b'] -> '{a, b}'
1066
+ const pgPath = Array.isArray(pathValue) ? `{${pathValue.join(', ')}}` : `{${pathValue}}`;
1067
+ const columnRef = this.escapeId(field);
1068
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1069
+ if (compareValue !== undefined) {
1070
+ values.push(compareValue);
1071
+ return `${columnRef} #> '${pgPath}' = ?`;
1072
+ }
1073
+ return `${columnRef} #> '${pgPath}' IS NOT NULL`;
1074
+ }
1075
+ // Fallback for other dialects
1076
+ const pathStr = Array.isArray(pathValue) ? pathValue.join('.') : pathValue;
1077
+ const jsonPath = pathStr.startsWith('$.') ? pathStr : `$.${pathStr}`;
1078
+ if (compareValue !== undefined) {
1079
+ values.push(compareValue);
1080
+ return `json_extract(${columnRef}, '${jsonPath}') = ?`;
1081
+ }
1082
+ return `json_extract(${columnRef}, '${jsonPath}') IS NOT NULL`;
1083
+ }
1084
+ return '';
1085
+ }
1086
+ // $jsonbExtractPathText - JSONB extract by path as text (PostgreSQL: #>>)
1087
+ // Extracts JSON by path as text, returns text
1088
+ if (operator.$jsonbExtractPathText !== undefined) {
1089
+ const extractValue = operator.$jsonbExtractPathText;
1090
+ if (typeof extractValue === 'object' && extractValue !== null && 'path' in extractValue) {
1091
+ const pathValue = extractValue.path;
1092
+ const compareValue = extractValue.value;
1093
+ // Convert path to PostgreSQL format: ['a', 'b'] -> '{a, b}'
1094
+ const pgPath = Array.isArray(pathValue) ? `{${pathValue.join(', ')}}` : `{${pathValue}}`;
1095
+ const columnRef = this.escapeId(field);
1096
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1097
+ if (compareValue !== undefined) {
1098
+ values.push(compareValue);
1099
+ return `${columnRef} #>> '${pgPath}' = ?`;
1100
+ }
1101
+ return `${columnRef} #>> '${pgPath}' IS NOT NULL`;
1102
+ }
1103
+ // Fallback for other dialects
1104
+ const pathStr = Array.isArray(pathValue) ? pathValue.join('.') : pathValue;
1105
+ const jsonPath = pathStr.startsWith('$.') ? pathStr : `$.${pathStr}`;
1106
+ if (compareValue !== undefined) {
1107
+ values.push(compareValue);
1108
+ return `json_extract(${columnRef}, '${jsonPath}') = ?`;
1109
+ }
1110
+ return `json_extract(${columnRef}, '${jsonPath}') IS NOT NULL`;
1111
+ }
1112
+ return '';
1113
+ }
1114
+ // $key - Get value at key path in JSON
1115
+ // Supports: { $key: 'address.city' } or { $key: { path: 'address.city', value: 'NYC' } }
1116
+ if (operator.$key !== undefined) {
1117
+ const keyValue = operator.$key;
1118
+ if (typeof keyValue === 'string') {
1119
+ // Just a key path, return the JSON extraction
1120
+ return `json_extract(${this.escapeId(field)}, '$.${keyValue}')`;
1121
+ }
1122
+ else if (typeof keyValue === 'object' && keyValue !== null && 'path' in keyValue) {
1123
+ // Key path with value comparison
1124
+ const keyObj = keyValue;
1125
+ const path = keyObj.path;
1126
+ const compareValue = keyObj.value;
1127
+ values.push(compareValue);
1128
+ return `json_extract(${this.escapeId(field)}, '$.${path}') = ?`;
1129
+ }
1130
+ // Fallback
1131
+ values.push(operator.$key);
1132
+ return `${this.escapeId(field)} = ?`;
1133
+ }
1134
+ // $jsonConcat - JSONB concatenation (PostgreSQL: ||)
1135
+ if (operator.$jsonConcat !== undefined) {
1136
+ const concatValue = operator.$jsonConcat;
1137
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1138
+ values.push(JSON.stringify(concatValue));
1139
+ return `${this.escapeId(field)} || ?`;
1140
+ }
1141
+ // Fallback for other dialects - use string concatenation
1142
+ values.push(JSON.stringify(concatValue));
1143
+ return `${this.escapeId(field)} = ?`;
1144
+ }
1145
+ // $jsonDelete - JSONB delete key (PostgreSQL: -)
1146
+ if (operator.$jsonDelete !== undefined) {
1147
+ const deleteKey = operator.$jsonDelete;
1148
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1149
+ return `${this.escapeId(field)} - '${deleteKey}'`;
1150
+ }
1151
+ // Fallback - not supported
1152
+ throw new Error('JSON key deletion is only supported in PostgreSQL');
1153
+ }
1154
+ // $jsonDeletePath - JSONB delete by path (PostgreSQL: #-)
1155
+ if (operator.$jsonDeletePath !== undefined) {
1156
+ const deletePath = operator.$jsonDeletePath;
1157
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1158
+ const pgPath = Array.isArray(deletePath)
1159
+ ? `{${deletePath.join(', ')}}`
1160
+ : `{${deletePath}}`;
1161
+ return `${this.escapeId(field)} #- '${pgPath}'`;
1162
+ }
1163
+ // Fallback - not supported
1164
+ throw new Error('JSON path deletion is only supported in PostgreSQL');
1165
+ }
1166
+ // $jsonPathExists - JSON path exists (PostgreSQL: @?)
1167
+ if (operator.$jsonPathExists !== undefined) {
1168
+ const pathValue = operator.$jsonPathExists;
1169
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1170
+ return `${this.escapeId(field)} @? '${pathValue}'`;
1171
+ }
1172
+ // Fallback for other dialects - use JSON_EXISTS or similar
1173
+ return `${this.escapeId(field)} @? '${pathValue}'`;
1174
+ }
1175
+ // $jsonPathQuery - JSON path query (PostgreSQL: @@)
1176
+ if (operator.$jsonPathQuery !== undefined) {
1177
+ const pathValue = operator.$jsonPathQuery;
1178
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1179
+ return `${this.escapeId(field)} @@ '${pathValue}'`;
1180
+ }
1181
+ // Fallback for other dialects
1182
+ return `${this.escapeId(field)} @@ '${pathValue}'`;
1183
+ }
1184
+ // $jsonTypeOf - JSON type of (PostgreSQL: json_typeof)
1185
+ if (operator.$jsonTypeOf !== undefined) {
1186
+ const typeValue = operator.$jsonTypeOf;
1187
+ if (this.options.dialectName === 'postgres' || this.options.dialectName === 'postgresql') {
1188
+ values.push(typeValue);
1189
+ return `json_typeof(${this.escapeId(field)}) = ?`;
1190
+ }
1191
+ // Fallback - not supported
1192
+ throw new Error('JSON type detection is only supported in PostgreSQL');
1193
+ }
1194
+ // $col - Column reference for comparisons (e.g., WHERE column1 = column2)
1195
+ if (operator.$col !== undefined) {
1196
+ const colName = String(operator.$col);
1197
+ return `${this.escapeId(field)} = ${this.escapeId(colName)}`;
1198
+ }
1199
+ // $match - Full-text search (MySQL MATCH AGAINST)
1200
+ // Format: { field: { $match: { columns: string[], mode: 'natural' | 'boolean', searchTerm: string } } }
1201
+ // or using Op.match(): { [Op.match(['title', 'body'])]: 'search term' }
1202
+ if (operator.$match !== undefined) {
1203
+ const matchValue = operator.$match;
1204
+ const searchTerm = typeof matchValue === 'string' ? matchValue : matchValue.searchTerm || '';
1205
+ const columns = matchValue.columns || [field];
1206
+ const mode = matchValue.mode || 'natural';
1207
+ // Helper to escape values using dialect
1208
+ const escapeValue = (val) => {
1209
+ if (this.options.dialect?.escape) {
1210
+ return this.options.dialect.escape(val);
1211
+ }
1212
+ if (typeof val === 'string') {
1213
+ return `'${val.replace(/'/g, "''")}'`;
1214
+ }
1215
+ return String(val);
1216
+ };
1217
+ // For MySQL dialect
1218
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1219
+ const columnList = columns.map((c) => this.escapeId(c)).join(', ');
1220
+ const modeStr = mode === 'boolean' ? 'IN BOOLEAN MODE' : 'IN NATURAL LANGUAGE MODE';
1221
+ return `MATCH(${columnList}) AGAINST(${escapeValue(searchTerm)} ${modeStr})`;
1222
+ }
1223
+ // For PostgreSQL, use tsvector/tsquery
1224
+ if (this.options.dialectName === 'postgres') {
1225
+ const columnList = columns.map((c) => this.escapeId(c)).join(" || ' ' || ");
1226
+ values.push(searchTerm);
1227
+ return `to_tsvector('english', ${columnList}) @@ to_tsquery('english', ?)`;
1228
+ }
1229
+ // Default fallback for SQLite (no FTS support)
1230
+ values.push(searchTerm);
1231
+ return `${this.escapeId(field)} LIKE ?`;
1232
+ }
1233
+ // $tsvector - PostgreSQL to_tsvector
1234
+ if (operator.$tsvector !== undefined) {
1235
+ const tsvectorValue = operator.$tsvector;
1236
+ const column = tsvectorValue.column || field;
1237
+ const config = tsvectorValue.config || 'english';
1238
+ // This is typically used with @@ operator in PostgreSQL
1239
+ return `to_tsvector('${config}', ${this.escapeId(column)})`;
1240
+ }
1241
+ // $tsquery - PostgreSQL to_tsquery
1242
+ if (operator.$tsquery !== undefined) {
1243
+ const tsqueryValue = operator.$tsquery;
1244
+ const query = tsqueryValue.query;
1245
+ const config = tsqueryValue.config || 'english';
1246
+ if (query) {
1247
+ values.push(query);
1248
+ return `to_tsquery('${config}', ?)`;
1249
+ }
1250
+ // Handle case where tsquery is used with a value: { field: { $tsquery: 'term' } }
1251
+ return `to_tsquery('${config}', ?)`;
1252
+ }
1253
+ // $plainTsquery - PostgreSQL plainto_tsquery
1254
+ if (operator.$plainTsquery !== undefined) {
1255
+ const plainTsqueryValue = operator.$plainTsquery;
1256
+ const query = plainTsqueryValue.query;
1257
+ const config = plainTsqueryValue.config || 'english';
1258
+ if (query) {
1259
+ values.push(query);
1260
+ return `plainto_tsquery('${config}', ?)`;
1261
+ }
1262
+ return `plainto_tsquery('${config}', ?)`;
1263
+ }
1264
+ // $phraseTsquery - PostgreSQL phraseto_tsquery
1265
+ if (operator.$phraseTsquery !== undefined) {
1266
+ const phraseTsqueryValue = operator.$phraseTsquery;
1267
+ const query = phraseTsqueryValue.query;
1268
+ const config = phraseTsqueryValue.config || 'english';
1269
+ if (query) {
1270
+ values.push(query);
1271
+ return `phraseto_tsquery('${config}', ?)`;
1272
+ }
1273
+ return `phraseto_tsquery('${config}', ?)`;
1274
+ }
1275
+ // ============================================
1276
+ // Spatial/Geometric operators
1277
+ // ============================================
1278
+ // Helper to convert geometry value to SQL
1279
+ const buildGeometrySql = (geom, srid) => {
1280
+ if (typeof geom === 'string') {
1281
+ // WKT string - wrap with ST_GeomFromText
1282
+ const sridStr = srid ? `, ${srid}` : '';
1283
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1284
+ return `ST_GeomFromText('${geom}'${sridStr})`;
1285
+ }
1286
+ return `ST_GeomFromText('${geom}'${sridStr})`;
1287
+ }
1288
+ if (typeof geom === 'object' && geom !== null && 'type' in geom && 'coordinates' in geom) {
1289
+ // GeoJSON object
1290
+ const sridStr = srid ? `, ${srid}` : '';
1291
+ return `ST_GeomFromGeoJSON('${JSON.stringify(geom)}'${sridStr})`;
1292
+ }
1293
+ // Just return as-is (assuming it's already a column reference)
1294
+ return String(geom);
1295
+ };
1296
+ // $stDistance - distance between two geometries
1297
+ if (operator.$stDistance !== undefined) {
1298
+ const distOptions = operator.$stDistance;
1299
+ const from = distOptions.from;
1300
+ const distance = distOptions.distance;
1301
+ const srid = distOptions.srid || 4326;
1302
+ const fromSql = buildGeometrySql(from, srid);
1303
+ const fieldGeom = this.escapeId(field);
1304
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1305
+ // MySQL: ST_Distance returns distance in units of the SRID
1306
+ values.push(distance);
1307
+ return `ST_Distance(${fieldGeom}, ST_GeomFromText('${typeof from === 'string' ? from : ''}', ${srid})) < ?`;
1308
+ }
1309
+ // PostgreSQL: ST_Distance for geometry, ST_Distance for geography (in meters)
1310
+ values.push(distance);
1311
+ return `ST_Distance(${fieldGeom}, ${fromSql}) < ?`;
1312
+ }
1313
+ // $stWithin - geometry A is within geometry B
1314
+ if (operator.$stWithin !== undefined) {
1315
+ const withinOptions = operator.$stWithin;
1316
+ const geometry = withinOptions.geometry;
1317
+ const srid = withinOptions.srid || 4326;
1318
+ const geomSql = buildGeometrySql(geometry, srid);
1319
+ const fieldGeom = this.escapeId(field);
1320
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1321
+ return `ST_Within(${fieldGeom}, ${geomSql})`;
1322
+ }
1323
+ // PostgreSQL
1324
+ return `ST_Within(${fieldGeom}, ${geomSql})`;
1325
+ }
1326
+ // $stContains - geometry A contains geometry B
1327
+ if (operator.$stContains !== undefined) {
1328
+ const containsOptions = operator.$stContains;
1329
+ const geometry = containsOptions.geometry;
1330
+ const srid = containsOptions.srid || 4326;
1331
+ const geomSql = buildGeometrySql(geometry, srid);
1332
+ const fieldGeom = this.escapeId(field);
1333
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1334
+ return `ST_Contains(${fieldGeom}, ${geomSql})`;
1335
+ }
1336
+ // PostgreSQL
1337
+ return `ST_Contains(${fieldGeom}, ${geomSql})`;
1338
+ }
1339
+ // $stIntersects - two geometries intersect
1340
+ if (operator.$stIntersects !== undefined) {
1341
+ const intersectsOptions = operator.$stIntersects;
1342
+ const geometry = intersectsOptions.geometry;
1343
+ const srid = intersectsOptions.srid || 4326;
1344
+ const geomSql = buildGeometrySql(geometry, srid);
1345
+ const fieldGeom = this.escapeId(field);
1346
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1347
+ return `ST_Intersects(${fieldGeom}, ${geomSql})`;
1348
+ }
1349
+ // PostgreSQL
1350
+ return `ST_Intersects(${fieldGeom}, ${geomSql})`;
1351
+ }
1352
+ // $stDWithin - geometries are within a given distance
1353
+ if (operator.$stDWithin !== undefined) {
1354
+ const dWithinOptions = operator.$stDWithin;
1355
+ const from = dWithinOptions.from;
1356
+ const distance = dWithinOptions.distance;
1357
+ const srid = dWithinOptions.srid || 4326;
1358
+ const spheroid = dWithinOptions.spheroid !== false;
1359
+ const fromSql = buildGeometrySql(from, srid);
1360
+ const fieldGeom = this.escapeId(field);
1361
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1362
+ // MySQL 8.0+ uses ST_DWithin
1363
+ values.push(distance);
1364
+ return `ST_DWithin(${fieldGeom}, ST_GeomFromText('${typeof from === 'string' ? from : ''}', ${srid}), ?)`;
1365
+ }
1366
+ // PostgreSQL: ST_DWithin for geometry, ST_DWithin for geography
1367
+ if (spheroid) {
1368
+ values.push(distance);
1369
+ return `ST_DWithin(${fieldGeom}, ${fromSql}, ?)`;
1370
+ }
1371
+ values.push(distance);
1372
+ return `ST_DWithin(${fieldGeom}, ${fromSql}, ?)`;
1373
+ }
1374
+ // $stCrosses - two geometries cross
1375
+ if (operator.$stCrosses !== undefined) {
1376
+ const crossesOptions = operator.$stCrosses;
1377
+ const geometry = crossesOptions.geometry;
1378
+ const srid = crossesOptions.srid || 4326;
1379
+ const geomSql = buildGeometrySql(geometry, srid);
1380
+ const fieldGeom = this.escapeId(field);
1381
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1382
+ return `ST_Crosses(${fieldGeom}, ${geomSql})`;
1383
+ }
1384
+ return `ST_Crosses(${fieldGeom}, ${geomSql})`;
1385
+ }
1386
+ // $stOverlaps - two geometries overlap
1387
+ if (operator.$stOverlaps !== undefined) {
1388
+ const overlapsOptions = operator.$stOverlaps;
1389
+ const geometry = overlapsOptions.geometry;
1390
+ const srid = overlapsOptions.srid || 4326;
1391
+ const geomSql = buildGeometrySql(geometry, srid);
1392
+ const fieldGeom = this.escapeId(field);
1393
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1394
+ return `ST_Overlaps(${fieldGeom}, ${geomSql})`;
1395
+ }
1396
+ return `ST_Overlaps(${fieldGeom}, ${geomSql})`;
1397
+ }
1398
+ // $stTouches - two geometries touch
1399
+ if (operator.$stTouches !== undefined) {
1400
+ const touchesOptions = operator.$stTouches;
1401
+ const geometry = touchesOptions.geometry;
1402
+ const srid = touchesOptions.srid || 4326;
1403
+ const geomSql = buildGeometrySql(geometry, srid);
1404
+ const fieldGeom = this.escapeId(field);
1405
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1406
+ return `ST_Touches(${fieldGeom}, ${geomSql})`;
1407
+ }
1408
+ return `ST_Touches(${fieldGeom}, ${geomSql})`;
1409
+ }
1410
+ // $stEquals - two geometries are equal
1411
+ if (operator.$stEquals !== undefined) {
1412
+ const equalsOptions = operator.$stEquals;
1413
+ const geometry = equalsOptions.geometry;
1414
+ const srid = equalsOptions.srid || 4326;
1415
+ const geomSql = buildGeometrySql(geometry, srid);
1416
+ const fieldGeom = this.escapeId(field);
1417
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1418
+ return `ST_Equals(${fieldGeom}, ${geomSql})`;
1419
+ }
1420
+ return `ST_Equals(${fieldGeom}, ${geomSql})`;
1421
+ }
1422
+ // $stIsValid - check if geometry is valid
1423
+ if (operator.$stIsValid !== undefined) {
1424
+ const fieldGeom = this.escapeId(field);
1425
+ if (this.options.dialectName === 'mysql' || this.options.dialectName === 'mariadb') {
1426
+ return `ST_IsValid(${fieldGeom})`;
1427
+ }
1428
+ return `ST_IsValid(${fieldGeom})`;
1429
+ }
1430
+ // Handle nested object as implicit equality
1431
+ values.push(operator);
1432
+ return `${this.escapeId(field)} = ?`;
1433
+ }
1434
+ /**
1435
+ * Escape an identifier (column/table name)
1436
+ */
1437
+ escapeId(identifier) {
1438
+ const id = String(identifier ?? '');
1439
+ if (this.options.dialect?.escapeId) {
1440
+ return this.options.dialect.escapeId(id);
1441
+ }
1442
+ // Default quoting
1443
+ return `"${id.replace(/"/g, '""')}"`;
1444
+ }
1445
+ /**
1446
+ * Escape a value for use in a query
1447
+ */
1448
+ escapeValue(value) {
1449
+ if (value === null) {
1450
+ return 'NULL';
1451
+ }
1452
+ if (typeof value === 'string') {
1453
+ return `'${value.replace(/'/g, "''")}'`;
1454
+ }
1455
+ if (typeof value === 'number') {
1456
+ return String(value);
1457
+ }
1458
+ if (typeof value === 'boolean') {
1459
+ return value ? 'TRUE' : 'FALSE';
1460
+ }
1461
+ if (value instanceof Date) {
1462
+ return `'${value.toISOString()}'`;
1463
+ }
1464
+ // Default: JSON stringify
1465
+ return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
1466
+ }
1467
+ /**
1468
+ * Build a PostgreSQL array literal from an array value
1469
+ * e.g., ['a', 'b'] => ARRAY['a', 'b']
1470
+ */
1471
+ buildArrayLiteral(value) {
1472
+ if (!Array.isArray(value)) {
1473
+ // Single value - wrap in array
1474
+ const escaped = this.options.dialect?.escape
1475
+ ? this.options.dialect.escape(value)
1476
+ : this.escapeValue(value);
1477
+ return `ARRAY[${escaped}]`;
1478
+ }
1479
+ if (value.length === 0) {
1480
+ return 'ARRAY[]';
1481
+ }
1482
+ const escapedValues = value.map((v) => {
1483
+ return this.options.dialect?.escape ? this.options.dialect.escape(v) : this.escapeValue(v);
1484
+ });
1485
+ return `ARRAY[${escapedValues.join(', ')}]`;
1486
+ }
1487
+ /**
1488
+ * Build a JSON extraction expression based on the dialect
1489
+ * SQLite/MySQL: json_extract(column, '$.path')
1490
+ * PostgreSQL: column->>'path' or #>>'{path}'
1491
+ *
1492
+ * @param field - The JSON/JSONB column name
1493
+ * @param path - The JSON path to extract (e.g., 'settings.theme' or '$.settings.theme')
1494
+ * @returns SQL fragment for JSON extraction
1495
+ */
1496
+ buildJsonExtract(field, path) {
1497
+ const columnRef = this.escapeId(field);
1498
+ const normalizedPath = path.startsWith('$.') ? path : `$.${path}`;
1499
+ // Get dialect name from options if available
1500
+ const dialectName = this.options?.dialectName;
1501
+ // PostgreSQL uses different syntax
1502
+ if (dialectName === 'postgres' || dialectName === 'postgresql') {
1503
+ // Convert path to PostgreSQL format: $.settings.theme -> 'settings', 'theme'
1504
+ const pathParts = normalizedPath.substring(2).split('.');
1505
+ const pgPath = pathParts.map((p) => `'${p}'`).join(', ');
1506
+ return `${columnRef} #>> '{${pgPath}}'`;
1507
+ }
1508
+ // SQLite and MySQL use JSON_EXTRACT / json_extract
1509
+ return `json_extract(${columnRef}, '${normalizedPath}')`;
1510
+ }
1511
+ /**
1512
+ * Build a JSON path query - alias for buildJsonExtract for compatibility
1513
+ */
1514
+ jsonExtract(field, path) {
1515
+ return this.buildJsonExtract(field, path);
1516
+ }
1517
+ }
1518
+ exports.WhereBuilder = WhereBuilder;
1519
+ /**
1520
+ * Create a WhereBuilder instance with default options
1521
+ */
1522
+ function createWhereBuilder(options) {
1523
+ return new WhereBuilder(options);
1524
+ }
1525
+ /**
1526
+ * Quick helper to build a WHERE clause
1527
+ */
1528
+ function buildWhereClause(where, options) {
1529
+ const builder = new WhereBuilder(options);
1530
+ return builder.build(where);
1531
+ }
1532
+ exports.default = WhereBuilder;