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,2690 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Cast = exports.SortDirection = exports.Operators = exports.Op = void 0;
|
|
4
|
+
exports.isColumnReference = isColumnReference;
|
|
5
|
+
exports.isColOperator = isColOperator;
|
|
6
|
+
exports.isOperator = isOperator;
|
|
7
|
+
exports.getOperatorString = getOperatorString;
|
|
8
|
+
exports.operatorToWhereKey = operatorToWhereKey;
|
|
9
|
+
exports.where = where;
|
|
10
|
+
exports.and = and;
|
|
11
|
+
exports.or = or;
|
|
12
|
+
exports.not = not;
|
|
13
|
+
exports.eq = eq;
|
|
14
|
+
exports.ne = ne;
|
|
15
|
+
exports.gt = gt;
|
|
16
|
+
exports.gte = gte;
|
|
17
|
+
exports.lt = lt;
|
|
18
|
+
exports.lte = lte;
|
|
19
|
+
exports.like = like;
|
|
20
|
+
exports.notLike = notLike;
|
|
21
|
+
exports.inOp = inOp;
|
|
22
|
+
exports.in = inOp;
|
|
23
|
+
exports.notIn = notIn;
|
|
24
|
+
exports.between = between;
|
|
25
|
+
exports.notBetween = notBetween;
|
|
26
|
+
exports.isNull = isNull;
|
|
27
|
+
exports.isNotNull = isNotNull;
|
|
28
|
+
exports.notExists = notExists;
|
|
29
|
+
exports.exists = exists;
|
|
30
|
+
exports.notInSubquery = notInSubquery;
|
|
31
|
+
exports.inSubquery = inSubquery;
|
|
32
|
+
exports.antiJoin = antiJoin;
|
|
33
|
+
exports.exclude = exclude;
|
|
34
|
+
exports.iLike = iLike;
|
|
35
|
+
exports.notILike = notILike;
|
|
36
|
+
exports.regexp = regexp;
|
|
37
|
+
exports.notRegexp = notRegexp;
|
|
38
|
+
exports.iRegexp = iRegexp;
|
|
39
|
+
exports.notIRegexp = notIRegexp;
|
|
40
|
+
exports.contains = contains;
|
|
41
|
+
exports.overlap = overlap;
|
|
42
|
+
exports.arrayContains = arrayContains;
|
|
43
|
+
exports.arrayContainedBy = arrayContainedBy;
|
|
44
|
+
exports.arrayOverlaps = arrayOverlaps;
|
|
45
|
+
exports.arrayAny = arrayAny;
|
|
46
|
+
exports.arrayAll = arrayAll;
|
|
47
|
+
exports.startsWith = startsWith;
|
|
48
|
+
exports.notStartsWith = notStartsWith;
|
|
49
|
+
exports.endsWith = endsWith;
|
|
50
|
+
exports.notEndsWith = notEndsWith;
|
|
51
|
+
exports.substring = substring;
|
|
52
|
+
exports.notSubstring = notSubstring;
|
|
53
|
+
exports.anyOp = anyOp;
|
|
54
|
+
exports.any = anyOp;
|
|
55
|
+
exports.allOp = allOp;
|
|
56
|
+
exports.all = allOp;
|
|
57
|
+
exports.match = match;
|
|
58
|
+
exports.isOrderExpression = isOrderExpression;
|
|
59
|
+
exports.asc = asc;
|
|
60
|
+
exports.desc = desc;
|
|
61
|
+
exports.random = random;
|
|
62
|
+
exports.matchAgainst = matchAgainst;
|
|
63
|
+
exports.matchFulltext = matchFulltext;
|
|
64
|
+
exports.toTsvector = toTsvector;
|
|
65
|
+
exports.toTsquery = toTsquery;
|
|
66
|
+
exports.plainToTsquery = plainToTsquery;
|
|
67
|
+
exports.phraseToTsquery = phraseToTsquery;
|
|
68
|
+
exports.contained = contained;
|
|
69
|
+
exports.jsonKey = jsonKey;
|
|
70
|
+
exports.json = json;
|
|
71
|
+
exports.jsonContains = jsonContains;
|
|
72
|
+
exports.jsonHasKey = jsonHasKey;
|
|
73
|
+
exports.jsonConcat = jsonConcat;
|
|
74
|
+
exports.jsonDelete = jsonDelete;
|
|
75
|
+
exports.jsonDeletePath = jsonDeletePath;
|
|
76
|
+
exports.jsonPathExists = jsonPathExists;
|
|
77
|
+
exports.jsonPathQuery = jsonPathQuery;
|
|
78
|
+
exports.fn = fn;
|
|
79
|
+
exports.col = col;
|
|
80
|
+
exports.literal = literal;
|
|
81
|
+
exports.literalExpr = literalExpr;
|
|
82
|
+
exports.isFnExpression = isFnExpression;
|
|
83
|
+
exports.isColExpression = isColExpression;
|
|
84
|
+
exports.isLiteralExpression = isLiteralExpression;
|
|
85
|
+
exports.isRawLiteral = isRawLiteral;
|
|
86
|
+
exports.isCastExpression = isCastExpression;
|
|
87
|
+
exports.isExtractExpression = isExtractExpression;
|
|
88
|
+
exports.isConvExpression = isConvExpression;
|
|
89
|
+
exports.cast = cast;
|
|
90
|
+
exports.extract = extract;
|
|
91
|
+
exports.conv = conv;
|
|
92
|
+
exports.SUM = SUM;
|
|
93
|
+
exports.AVG = AVG;
|
|
94
|
+
exports.COUNT = COUNT;
|
|
95
|
+
exports.MAX = MAX;
|
|
96
|
+
exports.MIN = MIN;
|
|
97
|
+
/**
|
|
98
|
+
* Check if a value is a column reference
|
|
99
|
+
*/
|
|
100
|
+
function isColumnReference(value) {
|
|
101
|
+
return (value !== null && typeof value === 'object' && '$col' in value && typeof value.$col === 'string');
|
|
102
|
+
}
|
|
103
|
+
// Symbol-based operators for type safety
|
|
104
|
+
exports.Op = {
|
|
105
|
+
// Logical operators
|
|
106
|
+
and: Symbol('and'),
|
|
107
|
+
or: Symbol('or'),
|
|
108
|
+
not: Symbol('not'),
|
|
109
|
+
// Comparison operators
|
|
110
|
+
eq: Symbol('eq'),
|
|
111
|
+
is: Symbol('is'),
|
|
112
|
+
ne: Symbol('ne'),
|
|
113
|
+
gt: Symbol('gt'),
|
|
114
|
+
gte: Symbol('gte'),
|
|
115
|
+
lt: Symbol('lt'),
|
|
116
|
+
lte: Symbol('lte'),
|
|
117
|
+
// String/LIKE operators as Symbols (standard Prorm API)
|
|
118
|
+
like: Symbol('like'),
|
|
119
|
+
notLike: Symbol('notLike'),
|
|
120
|
+
iLike: Symbol('iLike'),
|
|
121
|
+
notILike: Symbol('notILike'),
|
|
122
|
+
startsWith: Symbol('startsWith'),
|
|
123
|
+
notStartsWith: Symbol('notStartsWith'),
|
|
124
|
+
endsWith: Symbol('endsWith'),
|
|
125
|
+
notEndsWith: Symbol('notEndsWith'),
|
|
126
|
+
substring: Symbol('substring'),
|
|
127
|
+
notSubstring: Symbol('notSubstring'),
|
|
128
|
+
// Column reference as Symbol
|
|
129
|
+
col: Symbol('col'),
|
|
130
|
+
in: Symbol('in'),
|
|
131
|
+
notIn: Symbol('notIn'),
|
|
132
|
+
between: Symbol('between'),
|
|
133
|
+
notBetween: Symbol('notBetween'),
|
|
134
|
+
isNull: Symbol('isNull'),
|
|
135
|
+
isNotNull: Symbol('isNotNull'),
|
|
136
|
+
exists: Symbol('exists'),
|
|
137
|
+
notExists: Symbol('notExists'),
|
|
138
|
+
// Additional operators
|
|
139
|
+
regexp: Symbol('regexp'),
|
|
140
|
+
notRegexp: Symbol('notRegexp'),
|
|
141
|
+
iRegexp: Symbol('iRegexp'),
|
|
142
|
+
notIRegexp: Symbol('notIRegexp'),
|
|
143
|
+
any: Symbol('any'),
|
|
144
|
+
all: Symbol('all'),
|
|
145
|
+
containsKey: Symbol('containsKey'),
|
|
146
|
+
containsKeyPath: Symbol('containsKeyPath'),
|
|
147
|
+
containsPath: Symbol('containsPath'),
|
|
148
|
+
strictLeft: Symbol('strictLeft'),
|
|
149
|
+
strictRight: Symbol('strictRight'),
|
|
150
|
+
noExtendRight: Symbol('noExtendRight'),
|
|
151
|
+
noExtendLeft: Symbol('noExtendLeft'),
|
|
152
|
+
adj: Symbol('adj'),
|
|
153
|
+
notAdj: Symbol('notAdj'),
|
|
154
|
+
// PostgreSQL Array operators
|
|
155
|
+
/**
|
|
156
|
+
* Array contains - checks if an array contains all specified elements
|
|
157
|
+
* PostgreSQL: @>
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* // Find products where tags contains both 'electronics' and 'sale'
|
|
162
|
+
* Product.findAll({ where: { tags: { [Op.arrayContains]: ['electronics', 'sale'] } } })
|
|
163
|
+
* // SQL: WHERE tags @> ARRAY['electronics', 'sale']
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
arrayContains: Symbol('arrayContains'),
|
|
167
|
+
/**
|
|
168
|
+
* Array contained by - checks if an array is contained by the specified elements
|
|
169
|
+
* PostgreSQL: <@
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* // Find products where tags is contained by ['electronics', 'sale', 'new']
|
|
174
|
+
* Product.findAll({ where: { tags: { [Op.arrayContainedBy]: ['electronics', 'sale'] } } })
|
|
175
|
+
* // SQL: WHERE tags <@ ARRAY['electronics', 'sale']
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
arrayContainedBy: Symbol('arrayContainedBy'),
|
|
179
|
+
/**
|
|
180
|
+
* Array overlaps - checks if arrays have common elements
|
|
181
|
+
* PostgreSQL: &&
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* // Find products where tags overlaps with ['electronics', 'sale']
|
|
186
|
+
* Product.findAll({ where: { tags: { [Op.arrayOverlaps]: ['electronics', 'sale'] } } })
|
|
187
|
+
* // SQL: WHERE tags && ARRAY['electronics', 'sale']
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
arrayOverlaps: Symbol('arrayOverlaps'),
|
|
191
|
+
/**
|
|
192
|
+
* Array ANY - checks if any element matches the condition
|
|
193
|
+
* PostgreSQL: ANY
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* // Find products where any tag equals 'electronics'
|
|
198
|
+
* Product.findAll({ where: { tags: { [Op.arrayAny]: 'electronics' } } })
|
|
199
|
+
* // SQL: WHERE 'electronics' = ANY(tags)
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
arrayAny: Symbol('arrayAny'),
|
|
203
|
+
/**
|
|
204
|
+
* Array ALL - checks if all elements match the condition
|
|
205
|
+
* PostgreSQL: ALL
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* // Find products where all prices are greater than 100
|
|
210
|
+
* Product.findAll({ where: { prices: { [Op.arrayAll]: { [Op.gt]: 100 } } } })
|
|
211
|
+
* // SQL: WHERE 100 > ALL(prices)
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
arrayAll: Symbol('arrayAll'),
|
|
215
|
+
// Search/LIKE operators for pattern matching
|
|
216
|
+
/**
|
|
217
|
+
* Starts with - checks if a string starts with the given value
|
|
218
|
+
* SQL: LIKE 'value%'
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* // Find users whose name starts with 'John'
|
|
223
|
+
* User.findAll({ where: { name: { [Op.startsWith]: 'John' } } })
|
|
224
|
+
* // SQL: WHERE name LIKE 'John%'
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
// Note: startsWith, notStartsWith, endsWith, notEndsWith, substring, notSubstring
|
|
228
|
+
// are now methods that return clause objects, not symbols
|
|
229
|
+
/**
|
|
230
|
+
* Ends with - checks if a string ends with the given value
|
|
231
|
+
* SQL: LIKE '%value'
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* // Find users whose email ends with '@gmail.com'
|
|
236
|
+
* User.findAll({ where: { email: { [Op.endsWith]: '@gmail.com' } } })
|
|
237
|
+
* // SQL: WHERE email LIKE '%@gmail.com'
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
// Note: endsWith, notEndsWith, substring, notSubstring are now methods
|
|
241
|
+
// JSON operators for JSON/JSONB columns
|
|
242
|
+
/**
|
|
243
|
+
* JSON contains - checks if JSON document contains the specified value
|
|
244
|
+
* PostgreSQL: @>
|
|
245
|
+
* MySQL: JSON_CONTAINS
|
|
246
|
+
* SQLite: JSON_EXTRACT
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* // Find users where preferences contains { theme: 'dark' }
|
|
251
|
+
* User.findAll({ where: { preferences: { [Op.contains]: { theme: 'dark' } } } })
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
contains: Symbol('contains'),
|
|
255
|
+
/**
|
|
256
|
+
* JSON is contained in - checks if the value is contained in the JSON column
|
|
257
|
+
* PostgreSQL: <@
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* // Find users where settings is contained in the specified JSON
|
|
262
|
+
* User.findAll({ where: { settings: { [Op.containedBy]: { theme: 'dark', lang: 'en' } } } })
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
containedBy: Symbol('containedBy'),
|
|
266
|
+
/**
|
|
267
|
+
* JSON key exists - checks if a key (or array index) exists in a JSON object/array
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* // Find users where 'role' key exists in data column
|
|
272
|
+
* User.findAll({ where: { data: { [Op.keyExists]: 'role' } } })
|
|
273
|
+
*
|
|
274
|
+
* // Find users where array index 0 exists
|
|
275
|
+
* User.findAll({ where: { tags: { [Op.keyExists]: '0' } } })
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
keyExists: Symbol('keyExists'),
|
|
279
|
+
/**
|
|
280
|
+
* JSON overlap - checks if JSON arrays overlap (have common elements)
|
|
281
|
+
* PostgreSQL: &&
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* // Find users where tags overlaps with ['admin', 'vip']
|
|
286
|
+
* User.findAll({ where: { tags: { [Op.overlap]: ['admin', 'vip'] } } })
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
overlap: Symbol('overlap'),
|
|
290
|
+
/**
|
|
291
|
+
* JSON key access - access a specific key in a JSON column
|
|
292
|
+
* PostgreSQL: ->> for text, -> for object
|
|
293
|
+
* MySQL: JSON_EXTRACT
|
|
294
|
+
* SQLite: JSON_EXTRACT
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* // Find users where data->>'name' equals 'John'
|
|
299
|
+
* User.findAll({ where: { data: { [Op.key]: 'name' } } })
|
|
300
|
+
* // For nested: { field: { [Op.key]: 'profile.name' } }
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
// Note: key is now a method, not a symbol
|
|
304
|
+
/**
|
|
305
|
+
* Column reference - reference another column in a WHERE clause
|
|
306
|
+
* Used for comparing one column to another
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* // Find orders where quantity equals available stock
|
|
311
|
+
* Order.findAll({ where: { quantity: { [Op.col]: 'available_stock' } } })
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
// Note: col is now a method, not a symbol
|
|
315
|
+
// Full-text search operators (use methods instead of symbols for query building)
|
|
316
|
+
match: Symbol('match'),
|
|
317
|
+
// Legacy JSON operator
|
|
318
|
+
$json: Symbol('json'),
|
|
319
|
+
/**
|
|
320
|
+
* JSON path explicit operator - explicit JSON path with field and path separated
|
|
321
|
+
* Convenience method combining field and path in one call
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* // Query with field and path separated
|
|
326
|
+
* User.findAll({
|
|
327
|
+
* where: {
|
|
328
|
+
* data: { [Op.jsonPath('settings', 'theme')]: 'dark' }
|
|
329
|
+
* }
|
|
330
|
+
* })
|
|
331
|
+
* // SQLite/MySQL: WHERE json_extract(data, '$.settings.theme') = 'dark'
|
|
332
|
+
* // PostgreSQL: WHERE data->>'settings'->>'theme' = 'dark'
|
|
333
|
+
*
|
|
334
|
+
* // Check if path exists (no value comparison)
|
|
335
|
+
* User.findAll({
|
|
336
|
+
* where: {
|
|
337
|
+
* data: { [Op.jsonPath('settings', 'theme')]: { [Op.ne]: null } }
|
|
338
|
+
* }
|
|
339
|
+
* })
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
jsonPath: Symbol('jsonPath'),
|
|
343
|
+
/**
|
|
344
|
+
* JSON contains - checks if a JSON column contains a specific value or object
|
|
345
|
+
* PostgreSQL: @> (contains), MySQL: JSON_CONTAINS, SQLite: json_each
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* // Find users where preferences contains { theme: 'dark' }
|
|
350
|
+
* User.findAll({ where: { preferences: { [Op.jsonContains]: { theme: 'dark' } } } })
|
|
351
|
+
* // SQL: WHERE preferences @> '{"theme":"dark"}' (PostgreSQL)
|
|
352
|
+
* // SQL: WHERE JSON_CONTAINS(preferences, '{"theme":"dark"}') (MySQL)
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
jsonContains: Symbol('jsonContains'),
|
|
356
|
+
/**
|
|
357
|
+
* JSON has key - checks if a JSON column has a specific key
|
|
358
|
+
* PostgreSQL: ? (jsonb_exists), MySQL: JSON_CONTAINS_PATH, SQLite: json_each
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```typescript
|
|
362
|
+
* // Find users where data column has 'role' key
|
|
363
|
+
* User.findAll({ where: { data: { [Op.jsonHasKey]: 'role' } } })
|
|
364
|
+
* // SQL: WHERE data ? 'role' (PostgreSQL)
|
|
365
|
+
* // SQL: WHERE JSON_CONTAINS_PATH(data, 'one', '$.role') (MySQL)
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
jsonHasKey: Symbol('jsonHasKey'),
|
|
369
|
+
/**
|
|
370
|
+
* JSONB extract - extracts a JSON object field (returns JSON)
|
|
371
|
+
* PostgreSQL: -> operator
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* // Extract field as JSON: data -> 'key'
|
|
376
|
+
* User.findAll({ where: { data: { [Op.jsonbExtract]: { path: 'key' } } } })
|
|
377
|
+
* // SQL: WHERE data -> 'key'
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
jsonbExtract: Symbol('jsonbExtract'),
|
|
381
|
+
/**
|
|
382
|
+
* JSONB extract text - extracts a JSON object field as text (returns text)
|
|
383
|
+
* PostgreSQL: ->> operator
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* // Extract field as text: data ->> 'key'
|
|
388
|
+
* User.findAll({ where: { data: { [Op.jsonbExtractText]: { path: 'key', value: 'someValue' } } } })
|
|
389
|
+
* // SQL: WHERE data ->> 'key' = 'someValue'
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
jsonbExtractText: Symbol('jsonbExtractText'),
|
|
393
|
+
/**
|
|
394
|
+
* JSONB extract path - extracts JSON by path (returns JSON)
|
|
395
|
+
* PostgreSQL: #> operator
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```typescript
|
|
399
|
+
* // Extract nested path as JSON: data #> '{a, b}'
|
|
400
|
+
* User.findAll({ where: { data: { [Op.jsonbExtractPath]: { path: ['a', 'b'] } } } })
|
|
401
|
+
* // SQL: WHERE data #> '{a, b}'
|
|
402
|
+
* ```
|
|
403
|
+
*/
|
|
404
|
+
jsonbExtractPath: Symbol('jsonbExtractPath'),
|
|
405
|
+
/**
|
|
406
|
+
* JSONB extract path text - extracts JSON by path as text (returns text)
|
|
407
|
+
* PostgreSQL: #>> operator
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```typescript
|
|
411
|
+
* // Extract nested path as text: data #>> '{a, b}'
|
|
412
|
+
* User.findAll({ where: { data: { [Op.jsonbExtractPathText]: { path: ['a', 'b'], value: 'someValue' } } } })
|
|
413
|
+
* // SQL: WHERE data #>> '{a, b}' = 'someValue'
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
jsonbExtractPathText: Symbol('jsonbExtractPathText'),
|
|
417
|
+
/**
|
|
418
|
+
* JSONB concatenation - concatenates two JSONB values
|
|
419
|
+
* PostgreSQL: || operator
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```typescript
|
|
423
|
+
* // Concatenate JSONB values: data || '{"key": "value"}'
|
|
424
|
+
* User.findAll({ where: { data: { [Op.jsonConcat]: { key: 'value' } } } })
|
|
425
|
+
* // SQL: WHERE data || '{"key": "value"}'
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
jsonConcat: Symbol('jsonConcat'),
|
|
429
|
+
/**
|
|
430
|
+
* JSONB delete key - deletes a key from JSONB object
|
|
431
|
+
* PostgreSQL: - operator
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* // Delete key from JSONB: data - 'key'
|
|
436
|
+
* User.findAll({ where: { data: { [Op.jsonDelete]: 'key' } } })
|
|
437
|
+
* // SQL: WHERE data - 'key'
|
|
438
|
+
* ```
|
|
439
|
+
*/
|
|
440
|
+
jsonDelete: Symbol('jsonDelete'),
|
|
441
|
+
/**
|
|
442
|
+
* JSONB delete by path - deletes a key from JSONB by path
|
|
443
|
+
* PostgreSQL: #- operator
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* // Delete by path: data #- '{a, b}'
|
|
448
|
+
* User.findAll({ where: { data: { [Op.jsonDeletePath]: ['a', 'b'] } } })
|
|
449
|
+
* // SQL: WHERE data #- '{a, b}'
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
jsonDeletePath: Symbol('jsonDeletePath'),
|
|
453
|
+
/**
|
|
454
|
+
* JSON path exists - checks if a JSON path exists and returns boolean
|
|
455
|
+
* PostgreSQL: @? operator
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* // Check if path exists: data @? '$.key'
|
|
460
|
+
* User.findAll({ where: { data: { [Op.jsonPathExists]: '$.key' } } })
|
|
461
|
+
* // SQL: WHERE data @? '$.key'
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
jsonPathExists: Symbol('jsonPathExists'),
|
|
465
|
+
/**
|
|
466
|
+
* JSON path query - evaluates JSON path and returns result
|
|
467
|
+
* PostgreSQL: @@ operator
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* // Query JSON path: data @@ '$.key'
|
|
472
|
+
* User.findAll({ where: { data: { [Op.jsonPathQuery]: '$.key' } } })
|
|
473
|
+
* // SQL: WHERE data @@ '$.key'
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
jsonPathQuery: Symbol('jsonPathQuery'),
|
|
477
|
+
/**
|
|
478
|
+
* JSON type of - returns the type of a JSON value
|
|
479
|
+
* PostgreSQL: json_typeof function
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* // Get JSON type: json_typeof(data)
|
|
484
|
+
* User.findAll({ where: { data: { [Op.jsonTypeOf]: 'object' } } })
|
|
485
|
+
* // SQL: WHERE json_typeof(data) = 'object'
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
jsonTypeOf: Symbol('jsonTypeOf'),
|
|
489
|
+
/**
|
|
490
|
+
* Create a JSON column path reference for querying
|
|
491
|
+
* Returns a Symbol that can be used as a computed property key
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```typescript
|
|
495
|
+
* // Using as computed property key (recommended)
|
|
496
|
+
* User.findAll({
|
|
497
|
+
* where: {
|
|
498
|
+
* settings: { [Op.json('$.theme')]: 'dark' }
|
|
499
|
+
* }
|
|
500
|
+
* })
|
|
501
|
+
* // SQL: WHERE json_extract(settings, '$.theme') = 'dark'
|
|
502
|
+
*
|
|
503
|
+
* // Using with path prefix
|
|
504
|
+
* User.findAll({
|
|
505
|
+
* where: {
|
|
506
|
+
* data: { [Op.json('address.city')]: 'NYC' }
|
|
507
|
+
* }
|
|
508
|
+
* })
|
|
509
|
+
* // SQL: WHERE json_extract(data, '$.address.city') = 'NYC'
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
json(path) {
|
|
513
|
+
// Use Symbol.for to create a consistent symbol that can be retrieved later
|
|
514
|
+
// The path is encoded in the symbol description
|
|
515
|
+
return Symbol.for(`json:${path}`);
|
|
516
|
+
},
|
|
517
|
+
/**
|
|
518
|
+
* Create a JSON key path query
|
|
519
|
+
* Useful for accessing specific keys in a JSON/JSONB column
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* ```typescript
|
|
523
|
+
* // Access JSON key 'name' from data column
|
|
524
|
+
* User.findAll({
|
|
525
|
+
* where: {
|
|
526
|
+
* data: { [Op.key]: 'name' }
|
|
527
|
+
* }
|
|
528
|
+
* })
|
|
529
|
+
*
|
|
530
|
+
* // Compare JSON key to a value
|
|
531
|
+
* User.findAll({
|
|
532
|
+
* where: {
|
|
533
|
+
* data: { [Op.key]: { path: 'name', value: 'John' } }
|
|
534
|
+
* }
|
|
535
|
+
* })
|
|
536
|
+
*
|
|
537
|
+
* // Nested key access
|
|
538
|
+
* User.findAll({
|
|
539
|
+
* where: {
|
|
540
|
+
* data: { [Op.key]: { path: 'profile.settings.theme', value: 'dark' } }
|
|
541
|
+
* }
|
|
542
|
+
* })
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
key(pathOrOptions) {
|
|
546
|
+
if (typeof pathOrOptions === 'string') {
|
|
547
|
+
return { $key: pathOrOptions };
|
|
548
|
+
}
|
|
549
|
+
return { $key: pathOrOptions };
|
|
550
|
+
},
|
|
551
|
+
/**
|
|
552
|
+
* Create a raw SQL literal
|
|
553
|
+
* Useful for embedding raw SQL expressions like NOW(), CURRENT_TIMESTAMP, etc.
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```typescript
|
|
557
|
+
* // Use with update to set current timestamp
|
|
558
|
+
* User.update({ lastLogin: Op.literal('NOW()') }, { where: { ... } })
|
|
559
|
+
*
|
|
560
|
+
* // Use in where clause to compare with current time
|
|
561
|
+
* User.findAll({
|
|
562
|
+
* where: {
|
|
563
|
+
* createdAt: { [Op.lt]: Op.literal('NOW()') }
|
|
564
|
+
* }
|
|
565
|
+
* })
|
|
566
|
+
*
|
|
567
|
+
* // Compare column to a literal value
|
|
568
|
+
* User.findAll({
|
|
569
|
+
* where: {
|
|
570
|
+
* updatedAt: { [Op.gt]: Op.literal('createdAt') }
|
|
571
|
+
* }
|
|
572
|
+
* })
|
|
573
|
+
*
|
|
574
|
+
* // Use with Op.where
|
|
575
|
+
* User.findAll({ where: Op.where(Op.col('created_at'), '>', Op.literal('NOW()')) })
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
literal(value) {
|
|
579
|
+
return { $literal: value };
|
|
580
|
+
},
|
|
581
|
+
/**
|
|
582
|
+
* Create a CAST expression for type casting
|
|
583
|
+
* SQL: CAST(value AS type)
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```typescript
|
|
587
|
+
* // CAST('2023-01-01' AS DATE)
|
|
588
|
+
* User.findAll({
|
|
589
|
+
* attributes: [[Op.cast('2023-01-01', 'DATE'), 'dateOnly']]
|
|
590
|
+
* })
|
|
591
|
+
*
|
|
592
|
+
* // CAST(column AS INTEGER)
|
|
593
|
+
* User.findAll({
|
|
594
|
+
* attributes: [[Op.cast(col('createdAt'), 'INTEGER'), 'dateInt']]
|
|
595
|
+
* })
|
|
596
|
+
*
|
|
597
|
+
* // CAST to DECIMAL
|
|
598
|
+
* User.findAll({
|
|
599
|
+
* attributes: [[Op.cast(col('price'), 'DECIMAL(10,2)'), 'priceDecimal']]
|
|
600
|
+
* })
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
cast(value, type) {
|
|
604
|
+
return cast(value, type);
|
|
605
|
+
},
|
|
606
|
+
/**
|
|
607
|
+
* Create an EXTRACT expression for extracting date parts
|
|
608
|
+
* SQL: EXTRACT(part FROM field)
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```typescript
|
|
612
|
+
* // EXTRACT(YEAR FROM createdAt)
|
|
613
|
+
* User.findAll({
|
|
614
|
+
* attributes: [[Op.extract('createdAt', 'year'), 'year']]
|
|
615
|
+
* })
|
|
616
|
+
*
|
|
617
|
+
* // EXTRACT(MONTH FROM order_date)
|
|
618
|
+
* User.findAll({
|
|
619
|
+
* attributes: [[Op.extract('orderDate', 'month'), 'month']]
|
|
620
|
+
* })
|
|
621
|
+
*
|
|
622
|
+
* // EXTRACT(HOUR FROM timestamp)
|
|
623
|
+
* User.findAll({
|
|
624
|
+
* attributes: [[Op.extract('createdAt', 'hour'), 'hour']]
|
|
625
|
+
* })
|
|
626
|
+
* ```
|
|
627
|
+
*/
|
|
628
|
+
extract(field, part) {
|
|
629
|
+
return extract(field, part);
|
|
630
|
+
},
|
|
631
|
+
/**
|
|
632
|
+
* Create a CONVERT expression for type conversion
|
|
633
|
+
* SQL: CONVERT(value, type) for MySQL, CAST for other dialects
|
|
634
|
+
*
|
|
635
|
+
* @example
|
|
636
|
+
* ```typescript
|
|
637
|
+
* // CONVERT(value, type)
|
|
638
|
+
* User.findAll({
|
|
639
|
+
* attributes: [[Op.conv(col('value'), null, 'CHAR'), 'strValue']]
|
|
640
|
+
* })
|
|
641
|
+
*
|
|
642
|
+
* // MySQL charset conversion
|
|
643
|
+
* User.findAll({
|
|
644
|
+
* attributes: [[Op.conv(col('name'), null, 'utf8mb4'), 'utf8Name']]
|
|
645
|
+
* })
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
conv(value, from, to) {
|
|
649
|
+
return conv(value, from, to);
|
|
650
|
+
},
|
|
651
|
+
/**
|
|
652
|
+
* Create a where clause with a column reference
|
|
653
|
+
* Supports both simple equality (2 args) and explicit operator (3 args)
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```typescript
|
|
657
|
+
* // Simple equality (2 args) - defaults to =
|
|
658
|
+
* Op.where(Op.col('username'), 'john')
|
|
659
|
+
* // => { $where: { $col: 'username', $eq: 'john' } }
|
|
660
|
+
*
|
|
661
|
+
* // Compare column to a value with explicit operator
|
|
662
|
+
* Op.where(Op.col('user.id'), '=', 1)
|
|
663
|
+
* // => { $where: { $col: 'user.id', $eq: 1 } }
|
|
664
|
+
*
|
|
665
|
+
* // Compare column to another column
|
|
666
|
+
* Op.where(Op.col('balance'), '>', Op.col('credit_limit'))
|
|
667
|
+
* // => { $where: { $col: 'balance', $gt: { $col: 'credit_limit' } } }
|
|
668
|
+
*
|
|
669
|
+
* // Using with Op.eq symbol
|
|
670
|
+
* Op.where(Op.col('user.id'), Op.eq, 1)
|
|
671
|
+
* // => { $where: { $col: 'user.id', $eq: 1 } }
|
|
672
|
+
*
|
|
673
|
+
* // Using with findAll
|
|
674
|
+
* User.findAll({ where: Op.where(Op.col('username'), 'john') })
|
|
675
|
+
* User.findAll({ where: Op.where('status', 'active') })
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
where(column, operator, value) {
|
|
679
|
+
// Handle 2-argument version: Op.where(column, value) - defaults to equality
|
|
680
|
+
if (value === undefined) {
|
|
681
|
+
const compareValue = operator;
|
|
682
|
+
// If column is a string (column name), return simple equality
|
|
683
|
+
if (typeof column === 'string') {
|
|
684
|
+
return { [column]: compareValue };
|
|
685
|
+
}
|
|
686
|
+
// If column is a column reference, use $where with equality
|
|
687
|
+
return {
|
|
688
|
+
$where: {
|
|
689
|
+
$col: column.$col,
|
|
690
|
+
$eq: compareValue,
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
// 3-argument version: Op.where(column, operator, value)
|
|
695
|
+
// Convert string operator to operator key
|
|
696
|
+
let operatorKey;
|
|
697
|
+
const op = operator;
|
|
698
|
+
if (typeof op === 'symbol') {
|
|
699
|
+
operatorKey = operatorToWhereKey(op);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
// Map string operators to their $ format
|
|
703
|
+
switch (op) {
|
|
704
|
+
case '=':
|
|
705
|
+
case '==':
|
|
706
|
+
operatorKey = '$eq';
|
|
707
|
+
break;
|
|
708
|
+
case '!=':
|
|
709
|
+
case '<>':
|
|
710
|
+
operatorKey = '$ne';
|
|
711
|
+
break;
|
|
712
|
+
case '>':
|
|
713
|
+
operatorKey = '$gt';
|
|
714
|
+
break;
|
|
715
|
+
case '>=':
|
|
716
|
+
operatorKey = '$gte';
|
|
717
|
+
break;
|
|
718
|
+
case '<':
|
|
719
|
+
operatorKey = '$lt';
|
|
720
|
+
break;
|
|
721
|
+
case '<=':
|
|
722
|
+
operatorKey = '$lte';
|
|
723
|
+
break;
|
|
724
|
+
case 'like':
|
|
725
|
+
operatorKey = '$like';
|
|
726
|
+
break;
|
|
727
|
+
case 'not like':
|
|
728
|
+
case 'not_like':
|
|
729
|
+
operatorKey = '$notLike';
|
|
730
|
+
break;
|
|
731
|
+
case 'in':
|
|
732
|
+
operatorKey = '$in';
|
|
733
|
+
break;
|
|
734
|
+
case 'not in':
|
|
735
|
+
case 'not_in':
|
|
736
|
+
operatorKey = '$notIn';
|
|
737
|
+
break;
|
|
738
|
+
case 'between':
|
|
739
|
+
operatorKey = '$between';
|
|
740
|
+
break;
|
|
741
|
+
case 'not between':
|
|
742
|
+
case 'not_between':
|
|
743
|
+
operatorKey = '$notBetween';
|
|
744
|
+
break;
|
|
745
|
+
case 'is null':
|
|
746
|
+
case 'is_null':
|
|
747
|
+
operatorKey = '$isNull';
|
|
748
|
+
break;
|
|
749
|
+
case 'is not null':
|
|
750
|
+
case 'is_not_null':
|
|
751
|
+
operatorKey = '$isNotNull';
|
|
752
|
+
break;
|
|
753
|
+
default:
|
|
754
|
+
operatorKey = `$${op}`;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Handle string column name
|
|
758
|
+
if (typeof column === 'string') {
|
|
759
|
+
return {
|
|
760
|
+
[column]: {
|
|
761
|
+
[operatorKey]: value,
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
$where: {
|
|
767
|
+
$col: column.$col,
|
|
768
|
+
[operatorKey]: value,
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
},
|
|
772
|
+
/**
|
|
773
|
+
* Create an ascending order expression
|
|
774
|
+
* SQL: ORDER BY field ASC
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* ```typescript
|
|
778
|
+
* // Order by name ascending
|
|
779
|
+
* User.findAll({ order: [Op.asc('name')] })
|
|
780
|
+
* // SQL: ORDER BY name ASC
|
|
781
|
+
*
|
|
782
|
+
* // Multiple order conditions
|
|
783
|
+
* User.findAll({ order: [Op.asc('name'), Op.desc('createdAt')] })
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
asc(field) {
|
|
787
|
+
return asc(field);
|
|
788
|
+
},
|
|
789
|
+
/**
|
|
790
|
+
* Create a descending order expression
|
|
791
|
+
* SQL: ORDER BY field DESC
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```typescript
|
|
795
|
+
* // Order by createdAt descending (newest first)
|
|
796
|
+
* User.findAll({ order: [Op.desc('createdAt')] })
|
|
797
|
+
* // SQL: ORDER BY createdAt DESC
|
|
798
|
+
*
|
|
799
|
+
* // Combined with asc
|
|
800
|
+
* User.findAll({ order: [Op.asc('name'), Op.desc('createdAt')] })
|
|
801
|
+
* ```
|
|
802
|
+
*/
|
|
803
|
+
desc(field) {
|
|
804
|
+
return desc(field);
|
|
805
|
+
},
|
|
806
|
+
/**
|
|
807
|
+
* Create a random order expression
|
|
808
|
+
* SQL: ORDER BY RANDOM() (SQLite/PostgreSQL) or ORDER BY RAND() (MySQL)
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* ```typescript
|
|
812
|
+
* // Random ordering (useful for sampling)
|
|
813
|
+
* User.findAll({ order: [Op.random()] })
|
|
814
|
+
* // SQLite: ORDER BY RANDOM()
|
|
815
|
+
* // MySQL: ORDER BY RAND()
|
|
816
|
+
* // PostgreSQL: ORDER BY RANDOM()
|
|
817
|
+
*
|
|
818
|
+
* // With limit for random sample
|
|
819
|
+
* User.findAll({ order: [Op.random()], limit: 5 })
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
random() {
|
|
823
|
+
return random();
|
|
824
|
+
},
|
|
825
|
+
/**
|
|
826
|
+
* Check if a value is not null
|
|
827
|
+
* Returns true if the value is not null or undefined
|
|
828
|
+
* Useful as a predicate for filtering arrays
|
|
829
|
+
*
|
|
830
|
+
* @example
|
|
831
|
+
* ```typescript
|
|
832
|
+
* // Filter out null values from an array
|
|
833
|
+
* const values = [1, null, 2, undefined, 3];
|
|
834
|
+
* const notNullValues = values.filter(Op.isNotNull);
|
|
835
|
+
* // => [1, 2, 3]
|
|
836
|
+
*
|
|
837
|
+
* // Using in array filter with objects
|
|
838
|
+
* const users = [{ name: 'John', age: null }, { name: 'Jane', age: 25 }];
|
|
839
|
+
* const withAge = users.filter(u => Op.isNotNull(u.age));
|
|
840
|
+
* // => [{ name: 'Jane', age: 25 }]
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
isNotNullPredicate(value) {
|
|
844
|
+
return value !== null && value !== undefined;
|
|
845
|
+
},
|
|
846
|
+
/**
|
|
847
|
+
* Create a full-text search MATCH AGAINST condition (MySQL)
|
|
848
|
+
* SQL: MATCH(columns) AGAINST(searchTerm [IN NATURAL LANGUAGE MODE | IN BOOLEAN MODE])
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* ```typescript
|
|
852
|
+
* // Natural language mode search
|
|
853
|
+
* Article.findAll({
|
|
854
|
+
* where: {
|
|
855
|
+
* [Op.matchAgainst(['title', 'body'])]: 'database'
|
|
856
|
+
* }
|
|
857
|
+
* })
|
|
858
|
+
* // SQL: WHERE MATCH(title, body) AGAINST('database' IN NATURAL LANGUAGE MODE)
|
|
859
|
+
*
|
|
860
|
+
* // Boolean mode search
|
|
861
|
+
* Article.findAll({
|
|
862
|
+
* where: {
|
|
863
|
+
* [Op.matchAgainst(['title', 'body'], { mode: 'boolean' })]: '+mysql -oracle'
|
|
864
|
+
* }
|
|
865
|
+
* })
|
|
866
|
+
* // SQL: WHERE MATCH(title, body) AGAINST('+mysql -oracle' IN BOOLEAN MODE)
|
|
867
|
+
* ```
|
|
868
|
+
*/
|
|
869
|
+
matchAgainst(columns, options) {
|
|
870
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
871
|
+
return {
|
|
872
|
+
$match: {
|
|
873
|
+
columns: cols,
|
|
874
|
+
mode: options?.mode || 'natural',
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
},
|
|
878
|
+
/**
|
|
879
|
+
* Create a full-text search condition (alias for matchAgainst with explicit mode)
|
|
880
|
+
* SQL: MATCH(columns) AGAINST(searchTerm IN NATURAL LANGUAGE MODE)
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* ```typescript
|
|
884
|
+
* Article.findAll({
|
|
885
|
+
* where: {
|
|
886
|
+
* [Op.matchFulltext(['title', 'body'])]: 'database'
|
|
887
|
+
* }
|
|
888
|
+
* })
|
|
889
|
+
* // SQL: WHERE MATCH(title, body) AGAINST('database' IN NATURAL LANGUAGE MODE)
|
|
890
|
+
* ```
|
|
891
|
+
*/
|
|
892
|
+
matchFulltext(columns) {
|
|
893
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
894
|
+
return {
|
|
895
|
+
$match: {
|
|
896
|
+
columns: cols,
|
|
897
|
+
mode: 'natural',
|
|
898
|
+
},
|
|
899
|
+
};
|
|
900
|
+
},
|
|
901
|
+
/**
|
|
902
|
+
* Create a PostgreSQL tsvector expression
|
|
903
|
+
* SQL: to_tsvector(config, column)
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```typescript
|
|
907
|
+
* Article.findAll({
|
|
908
|
+
* where: {
|
|
909
|
+
* [Op.toTsvector('title')]: { $tsquery: 'database' }
|
|
910
|
+
* }
|
|
911
|
+
* })
|
|
912
|
+
* // SQL: WHERE to_tsvector('english', title) @@ to_tsquery('english', 'database')
|
|
913
|
+
* ```
|
|
914
|
+
*/
|
|
915
|
+
toTsvector(column, config = 'english') {
|
|
916
|
+
return {
|
|
917
|
+
$tsvector: {
|
|
918
|
+
column,
|
|
919
|
+
config,
|
|
920
|
+
},
|
|
921
|
+
};
|
|
922
|
+
},
|
|
923
|
+
/**
|
|
924
|
+
* Create a PostgreSQL tsquery expression
|
|
925
|
+
* SQL: to_tsquery(config, query)
|
|
926
|
+
*
|
|
927
|
+
* @example
|
|
928
|
+
* ```typescript
|
|
929
|
+
* Article.findAll({
|
|
930
|
+
* where: {
|
|
931
|
+
* body: { [Op.toTsquery('database')]: true }
|
|
932
|
+
* }
|
|
933
|
+
* })
|
|
934
|
+
* // SQL: WHERE body @@ to_tsquery('english', 'database')
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
toTsquery(query, config = 'english') {
|
|
938
|
+
return {
|
|
939
|
+
$tsquery: {
|
|
940
|
+
query,
|
|
941
|
+
config,
|
|
942
|
+
},
|
|
943
|
+
};
|
|
944
|
+
},
|
|
945
|
+
// ============================================
|
|
946
|
+
// Spatial/Geometric operators
|
|
947
|
+
// ============================================
|
|
948
|
+
/**
|
|
949
|
+
* ST_Distance - calculate distance between two geometries
|
|
950
|
+
* MySQL: ST_Distance(geom1, geom2)
|
|
951
|
+
* PostgreSQL: ST_Distance(geom1, geom2) - for geometry, ST_Distance(geog1, geog2) for geography
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```typescript
|
|
955
|
+
* // Find locations within 100 meters of a point
|
|
956
|
+
* Location.findAll({
|
|
957
|
+
* where: {
|
|
958
|
+
* location: { [Op.stDWithin]: { from: 'POINT(0 0)', distance: 100, srid: 4326 } }
|
|
959
|
+
* }
|
|
960
|
+
* })
|
|
961
|
+
* ```
|
|
962
|
+
*/
|
|
963
|
+
stDistance: Symbol('stDistance'),
|
|
964
|
+
/**
|
|
965
|
+
* ST_Within - check if geometry A is within geometry B
|
|
966
|
+
* MySQL: ST_Within(geom1, geom2)
|
|
967
|
+
* PostgreSQL: ST_Within(geom1, geom2)
|
|
968
|
+
*
|
|
969
|
+
* @example
|
|
970
|
+
* ```typescript
|
|
971
|
+
* // Find locations within a polygon
|
|
972
|
+
* Location.findAll({
|
|
973
|
+
* where: {
|
|
974
|
+
* location: { [Op.stWithin]: { geometry: 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', srid: 4326 } }
|
|
975
|
+
* }
|
|
976
|
+
* })
|
|
977
|
+
* ```
|
|
978
|
+
*/
|
|
979
|
+
stWithin: Symbol('stWithin'),
|
|
980
|
+
/**
|
|
981
|
+
* ST_Contains - check if geometry A contains geometry B
|
|
982
|
+
* MySQL: ST_Contains(geom1, geom2)
|
|
983
|
+
* PostgreSQL: ST_Contains(geom1, geom2)
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```typescript
|
|
987
|
+
* // Find areas that contain a point
|
|
988
|
+
* Area.findAll({
|
|
989
|
+
* where: {
|
|
990
|
+
* boundary: { [Op.stContains]: { geometry: 'POINT(5 5)', srid: 4326 } }
|
|
991
|
+
* }
|
|
992
|
+
* })
|
|
993
|
+
* ```
|
|
994
|
+
*/
|
|
995
|
+
stContains: Symbol('stContains'),
|
|
996
|
+
/**
|
|
997
|
+
* ST_Intersects - check if two geometries intersect
|
|
998
|
+
* MySQL: ST_Intersects(geom1, geom2)
|
|
999
|
+
* PostgreSQL: ST_Intersects(geom1, geom2)
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* ```typescript
|
|
1003
|
+
* // Find locations that intersect with a polygon
|
|
1004
|
+
* Location.findAll({
|
|
1005
|
+
* where: {
|
|
1006
|
+
* location: { [Op.stIntersects]: { geometry: 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', srid: 4326 } }
|
|
1007
|
+
* }
|
|
1008
|
+
* })
|
|
1009
|
+
* ```
|
|
1010
|
+
*/
|
|
1011
|
+
stIntersects: Symbol('stIntersects'),
|
|
1012
|
+
/**
|
|
1013
|
+
* ST_DWithin - check if geometries are within a given distance
|
|
1014
|
+
* PostgreSQL: ST_DWithin (for both geometry and geography)
|
|
1015
|
+
* MySQL 8.0+: ST_Distance_Sphere or ST_DWithin (with care)
|
|
1016
|
+
*
|
|
1017
|
+
* @example
|
|
1018
|
+
* ```typescript
|
|
1019
|
+
* // Find locations within 1km of a point
|
|
1020
|
+
* Location.findAll({
|
|
1021
|
+
* where: {
|
|
1022
|
+
* location: { [Op.stDWithin]: { from: 'POINT(-122.4194 37.7749)', distance: 1000, srid: 4326 } }
|
|
1023
|
+
* }
|
|
1024
|
+
* })
|
|
1025
|
+
* ```
|
|
1026
|
+
*/
|
|
1027
|
+
stDWithin: Symbol('stDWithin'),
|
|
1028
|
+
/**
|
|
1029
|
+
* ST_Crosses - check if two geometries cross
|
|
1030
|
+
* MySQL: ST_Crosses(geom1, geom2)
|
|
1031
|
+
* PostgreSQL: ST_Crosses(geom1, geom2)
|
|
1032
|
+
*/
|
|
1033
|
+
stCrosses: Symbol('stCrosses'),
|
|
1034
|
+
/**
|
|
1035
|
+
* ST_Overlaps - check if two geometries overlap
|
|
1036
|
+
* MySQL: ST_Overlaps(geom1, geom2)
|
|
1037
|
+
* PostgreSQL: ST_Overlaps(geom1, geom2)
|
|
1038
|
+
*/
|
|
1039
|
+
stOverlaps: Symbol('stOverlaps'),
|
|
1040
|
+
/**
|
|
1041
|
+
* ST_Touches - check if two geometries touch
|
|
1042
|
+
* MySQL: ST_Touches(geom1, geom2)
|
|
1043
|
+
* PostgreSQL: ST_Touches(geom1, geom2)
|
|
1044
|
+
*/
|
|
1045
|
+
stTouches: Symbol('stTouches'),
|
|
1046
|
+
/**
|
|
1047
|
+
* ST_Equals - check if two geometries are equal
|
|
1048
|
+
* MySQL: ST_Equals(geom1, geom2)
|
|
1049
|
+
* PostgreSQL: ST_Equals(geom1, geom2)
|
|
1050
|
+
*/
|
|
1051
|
+
stEquals: Symbol('stEquals'),
|
|
1052
|
+
/**
|
|
1053
|
+
* ST_IsValid - check if a geometry is valid
|
|
1054
|
+
* MySQL: ST_IsValid(geom)
|
|
1055
|
+
* PostgreSQL: ST_IsValid(geom)
|
|
1056
|
+
*/
|
|
1057
|
+
stIsValid: Symbol('stIsValid'),
|
|
1058
|
+
};
|
|
1059
|
+
/**
|
|
1060
|
+
* Check if a value is a column reference or needs column reference handling
|
|
1061
|
+
*/
|
|
1062
|
+
function isColOperator(value) {
|
|
1063
|
+
return isColumnReference(value);
|
|
1064
|
+
}
|
|
1065
|
+
// Alias for backward compatibility and convenience
|
|
1066
|
+
exports.Operators = exports.Op;
|
|
1067
|
+
/**
|
|
1068
|
+
* Check if a value is a symbol operator
|
|
1069
|
+
*/
|
|
1070
|
+
function isOperator(value) {
|
|
1071
|
+
return typeof value === 'symbol';
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Get the string representation of an operator symbol
|
|
1075
|
+
*/
|
|
1076
|
+
function getOperatorString(op) {
|
|
1077
|
+
const opStr = op.description?.replace(/^\(.*?\)\s*/, '') || '';
|
|
1078
|
+
switch (opStr) {
|
|
1079
|
+
case 'and':
|
|
1080
|
+
return '$and';
|
|
1081
|
+
case 'or':
|
|
1082
|
+
return '$or';
|
|
1083
|
+
case 'not':
|
|
1084
|
+
return '$not';
|
|
1085
|
+
case 'eq':
|
|
1086
|
+
return '$eq';
|
|
1087
|
+
case 'ne':
|
|
1088
|
+
return '$ne';
|
|
1089
|
+
case 'gt':
|
|
1090
|
+
return '$gt';
|
|
1091
|
+
case 'gte':
|
|
1092
|
+
return '$gte';
|
|
1093
|
+
case 'lt':
|
|
1094
|
+
return '$lt';
|
|
1095
|
+
case 'lte':
|
|
1096
|
+
return '$lte';
|
|
1097
|
+
case 'like':
|
|
1098
|
+
return '$like';
|
|
1099
|
+
case 'notLike':
|
|
1100
|
+
return '$notLike';
|
|
1101
|
+
case 'iLike':
|
|
1102
|
+
return '$iLike';
|
|
1103
|
+
case 'notILike':
|
|
1104
|
+
return '$notILike';
|
|
1105
|
+
case 'in':
|
|
1106
|
+
return '$in';
|
|
1107
|
+
case 'notIn':
|
|
1108
|
+
return '$notIn';
|
|
1109
|
+
case 'between':
|
|
1110
|
+
return '$between';
|
|
1111
|
+
case 'notBetween':
|
|
1112
|
+
return '$notBetween';
|
|
1113
|
+
case 'isNull':
|
|
1114
|
+
return '$isNull';
|
|
1115
|
+
case 'isNotNull':
|
|
1116
|
+
return '$isNotNull';
|
|
1117
|
+
case 'startsWith':
|
|
1118
|
+
return '$startsWith';
|
|
1119
|
+
case 'notStartsWith':
|
|
1120
|
+
return '$notStartsWith';
|
|
1121
|
+
case 'endsWith':
|
|
1122
|
+
return '$endsWith';
|
|
1123
|
+
case 'notEndsWith':
|
|
1124
|
+
return '$notEndsWith';
|
|
1125
|
+
case 'substring':
|
|
1126
|
+
return '$substring';
|
|
1127
|
+
case 'notSubstring':
|
|
1128
|
+
return '$notSubstring';
|
|
1129
|
+
default:
|
|
1130
|
+
return `$${opStr}`;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Convert operator symbol to where clause key
|
|
1135
|
+
*/
|
|
1136
|
+
function operatorToWhereKey(op) {
|
|
1137
|
+
if (op === exports.Op.and)
|
|
1138
|
+
return '$and';
|
|
1139
|
+
if (op === exports.Op.or)
|
|
1140
|
+
return '$or';
|
|
1141
|
+
if (op === exports.Op.not)
|
|
1142
|
+
return '$not';
|
|
1143
|
+
if (op === exports.Op.eq)
|
|
1144
|
+
return '$eq';
|
|
1145
|
+
if (op === exports.Op.is)
|
|
1146
|
+
return '$is';
|
|
1147
|
+
if (op === exports.Op.ne)
|
|
1148
|
+
return '$ne';
|
|
1149
|
+
if (op === exports.Op.gt)
|
|
1150
|
+
return '$gt';
|
|
1151
|
+
if (op === exports.Op.gte)
|
|
1152
|
+
return '$gte';
|
|
1153
|
+
if (op === exports.Op.lt)
|
|
1154
|
+
return '$lt';
|
|
1155
|
+
if (op === exports.Op.lte)
|
|
1156
|
+
return '$lte';
|
|
1157
|
+
if (op === exports.Op.like)
|
|
1158
|
+
return '$like';
|
|
1159
|
+
if (op === exports.Op.notLike)
|
|
1160
|
+
return '$notLike';
|
|
1161
|
+
if (op === exports.Op.iLike)
|
|
1162
|
+
return '$iLike';
|
|
1163
|
+
if (op === exports.Op.notILike)
|
|
1164
|
+
return '$notILike';
|
|
1165
|
+
if (op === exports.Op.startsWith)
|
|
1166
|
+
return '$startsWith';
|
|
1167
|
+
if (op === exports.Op.notStartsWith)
|
|
1168
|
+
return '$notStartsWith';
|
|
1169
|
+
if (op === exports.Op.endsWith)
|
|
1170
|
+
return '$endsWith';
|
|
1171
|
+
if (op === exports.Op.notEndsWith)
|
|
1172
|
+
return '$notEndsWith';
|
|
1173
|
+
if (op === exports.Op.substring)
|
|
1174
|
+
return '$substring';
|
|
1175
|
+
if (op === exports.Op.notSubstring)
|
|
1176
|
+
return '$notSubstring';
|
|
1177
|
+
if (op === exports.Op.col)
|
|
1178
|
+
return '$col';
|
|
1179
|
+
if (op === exports.Op.in)
|
|
1180
|
+
return '$in';
|
|
1181
|
+
if (op === exports.Op.notIn)
|
|
1182
|
+
return '$notIn';
|
|
1183
|
+
if (op === exports.Op.between)
|
|
1184
|
+
return '$between';
|
|
1185
|
+
if (op === exports.Op.notBetween)
|
|
1186
|
+
return '$notBetween';
|
|
1187
|
+
if (op === exports.Op.isNull)
|
|
1188
|
+
return '$isNull';
|
|
1189
|
+
if (op === exports.Op.isNotNull)
|
|
1190
|
+
return '$isNotNull';
|
|
1191
|
+
if (op === exports.Op.exists)
|
|
1192
|
+
return '$exists';
|
|
1193
|
+
if (op === exports.Op.notExists)
|
|
1194
|
+
return '$notExists';
|
|
1195
|
+
if (op === exports.Op.regexp)
|
|
1196
|
+
return '$regexp';
|
|
1197
|
+
if (op === exports.Op.notRegexp)
|
|
1198
|
+
return '$notRegexp';
|
|
1199
|
+
if (op === exports.Op.iRegexp)
|
|
1200
|
+
return '$iRegexp';
|
|
1201
|
+
if (op === exports.Op.notIRegexp)
|
|
1202
|
+
return '$notIRegexp';
|
|
1203
|
+
if (op === exports.Op.any)
|
|
1204
|
+
return '$any';
|
|
1205
|
+
if (op === exports.Op.all)
|
|
1206
|
+
return '$all';
|
|
1207
|
+
if (op === exports.Op.contains)
|
|
1208
|
+
return '$contains';
|
|
1209
|
+
if (op === exports.Op.containedBy)
|
|
1210
|
+
return '$containedBy';
|
|
1211
|
+
if (op === exports.Op.overlap)
|
|
1212
|
+
return '$overlap';
|
|
1213
|
+
if (op === exports.Op.containsKey)
|
|
1214
|
+
return '$containsKey';
|
|
1215
|
+
if (op === exports.Op.containsKeyPath)
|
|
1216
|
+
return '$containsKeyPath';
|
|
1217
|
+
if (op === exports.Op.containsPath)
|
|
1218
|
+
return '$containsPath';
|
|
1219
|
+
if (op === exports.Op.arrayContains)
|
|
1220
|
+
return '$arrayContains';
|
|
1221
|
+
if (op === exports.Op.arrayContainedBy)
|
|
1222
|
+
return '$arrayContainedBy';
|
|
1223
|
+
if (op === exports.Op.arrayOverlaps)
|
|
1224
|
+
return '$arrayOverlaps';
|
|
1225
|
+
if (op === exports.Op.arrayAny)
|
|
1226
|
+
return '$arrayAny';
|
|
1227
|
+
if (op === exports.Op.arrayAll)
|
|
1228
|
+
return '$arrayAll';
|
|
1229
|
+
if (op === exports.Op.keyExists)
|
|
1230
|
+
return '$keyExists';
|
|
1231
|
+
if (op === exports.Op.key)
|
|
1232
|
+
return '$key';
|
|
1233
|
+
if (op === exports.Op.col)
|
|
1234
|
+
return '$col';
|
|
1235
|
+
if (op === exports.Op.json)
|
|
1236
|
+
return '$json';
|
|
1237
|
+
if (op === exports.Op.strictLeft)
|
|
1238
|
+
return '$strictLeft';
|
|
1239
|
+
if (op === exports.Op.strictRight)
|
|
1240
|
+
return '$strictRight';
|
|
1241
|
+
if (op === exports.Op.noExtendRight)
|
|
1242
|
+
return '$noExtendRight';
|
|
1243
|
+
if (op === exports.Op.noExtendLeft)
|
|
1244
|
+
return '$noExtendLeft';
|
|
1245
|
+
if (op === exports.Op.adj)
|
|
1246
|
+
return '$adj';
|
|
1247
|
+
if (op === exports.Op.notAdj)
|
|
1248
|
+
return '$notAdj';
|
|
1249
|
+
if (op === exports.Op.json)
|
|
1250
|
+
return '$json';
|
|
1251
|
+
if (op === exports.Op.jsonPath)
|
|
1252
|
+
return '$jsonPath';
|
|
1253
|
+
if (op === exports.Op.jsonContains)
|
|
1254
|
+
return '$jsonContains';
|
|
1255
|
+
if (op === exports.Op.jsonHasKey)
|
|
1256
|
+
return '$jsonHasKey';
|
|
1257
|
+
if (op === exports.Op.jsonConcat)
|
|
1258
|
+
return '$jsonConcat';
|
|
1259
|
+
if (op === exports.Op.jsonDelete)
|
|
1260
|
+
return '$jsonDelete';
|
|
1261
|
+
if (op === exports.Op.jsonDeletePath)
|
|
1262
|
+
return '$jsonDeletePath';
|
|
1263
|
+
if (op === exports.Op.jsonPathExists)
|
|
1264
|
+
return '$jsonPathExists';
|
|
1265
|
+
if (op === exports.Op.jsonPathQuery)
|
|
1266
|
+
return '$jsonPathQuery';
|
|
1267
|
+
if (op === exports.Op.jsonTypeOf)
|
|
1268
|
+
return '$jsonTypeOf';
|
|
1269
|
+
// JSONB extract operators
|
|
1270
|
+
if (op === exports.Op.jsonbExtract)
|
|
1271
|
+
return '$jsonbExtract';
|
|
1272
|
+
if (op === exports.Op.jsonbExtractText)
|
|
1273
|
+
return '$jsonbExtractText';
|
|
1274
|
+
if (op === exports.Op.jsonbExtractPath)
|
|
1275
|
+
return '$jsonbExtractPath';
|
|
1276
|
+
if (op === exports.Op.jsonbExtractPathText)
|
|
1277
|
+
return '$jsonbExtractPathText';
|
|
1278
|
+
// Spatial operators
|
|
1279
|
+
if (op === exports.Op.stDistance)
|
|
1280
|
+
return '$stDistance';
|
|
1281
|
+
if (op === exports.Op.stWithin)
|
|
1282
|
+
return '$stWithin';
|
|
1283
|
+
if (op === exports.Op.stContains)
|
|
1284
|
+
return '$stContains';
|
|
1285
|
+
if (op === exports.Op.stIntersects)
|
|
1286
|
+
return '$stIntersects';
|
|
1287
|
+
if (op === exports.Op.stDWithin)
|
|
1288
|
+
return '$stDWithin';
|
|
1289
|
+
if (op === exports.Op.stCrosses)
|
|
1290
|
+
return '$stCrosses';
|
|
1291
|
+
if (op === exports.Op.stOverlaps)
|
|
1292
|
+
return '$stOverlaps';
|
|
1293
|
+
if (op === exports.Op.stTouches)
|
|
1294
|
+
return '$stTouches';
|
|
1295
|
+
if (op === exports.Op.stEquals)
|
|
1296
|
+
return '$stEquals';
|
|
1297
|
+
if (op === exports.Op.stIsValid)
|
|
1298
|
+
return '$stIsValid';
|
|
1299
|
+
// Full-text search operators
|
|
1300
|
+
if (op === exports.Op.match)
|
|
1301
|
+
return '$match';
|
|
1302
|
+
if (op === exports.Op.matchFulltext)
|
|
1303
|
+
return '$match';
|
|
1304
|
+
if (op === exports.Op.toTsvector)
|
|
1305
|
+
return '$tsvector';
|
|
1306
|
+
if (op === exports.Op.toTsquery)
|
|
1307
|
+
return '$tsquery';
|
|
1308
|
+
// Note: startsWith, notStartsWith, endsWith, notEndsWith, substring, notSubstring
|
|
1309
|
+
// are now methods that return clause objects directly
|
|
1310
|
+
// Default - use symbol description, preserving its original casing so that
|
|
1311
|
+
// camelCase operator names normalize to matching camelCase `$key`s.
|
|
1312
|
+
const desc = op.description || '';
|
|
1313
|
+
const key = desc.match(/\((.*?)\)/)?.[1] || desc;
|
|
1314
|
+
return `$${key}`;
|
|
1315
|
+
}
|
|
1316
|
+
function where(columnOrCondition, operator, value) {
|
|
1317
|
+
// Handle the case where it's called with a single object argument
|
|
1318
|
+
if (typeof columnOrCondition === 'object' && columnOrCondition !== null) {
|
|
1319
|
+
const condition = columnOrCondition;
|
|
1320
|
+
// Handle nested OR: { OR: [...] } => { $or: [...] }
|
|
1321
|
+
if ('OR' in condition) {
|
|
1322
|
+
return { $or: condition.OR };
|
|
1323
|
+
}
|
|
1324
|
+
// Handle nested AND: { AND: [...] } => { $and: [...] }
|
|
1325
|
+
if ('AND' in condition) {
|
|
1326
|
+
return { $and: condition.AND };
|
|
1327
|
+
}
|
|
1328
|
+
// Handle combined OR/AND in a single object (e.g., { OR: [...], AND: [...] })
|
|
1329
|
+
const result = {};
|
|
1330
|
+
if ('$or' in condition) {
|
|
1331
|
+
result.$or = condition.$or;
|
|
1332
|
+
}
|
|
1333
|
+
if ('$and' in condition) {
|
|
1334
|
+
result.$and = condition.$and;
|
|
1335
|
+
}
|
|
1336
|
+
if ('$not' in condition) {
|
|
1337
|
+
result.$not = condition.$not;
|
|
1338
|
+
}
|
|
1339
|
+
// Merge any additional field conditions
|
|
1340
|
+
for (const key of Object.keys(condition)) {
|
|
1341
|
+
if (key !== 'OR' && key !== 'AND') {
|
|
1342
|
+
result[key] = condition[key];
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// If the result only has field conditions (no $or/$and/$not), return as-is
|
|
1346
|
+
// The WhereBuilder already handles these formats
|
|
1347
|
+
if (Object.keys(result).length === 0) {
|
|
1348
|
+
return condition;
|
|
1349
|
+
}
|
|
1350
|
+
return result;
|
|
1351
|
+
}
|
|
1352
|
+
// Handle three-argument form: where(column, operator, value)
|
|
1353
|
+
const column = columnOrCondition;
|
|
1354
|
+
return {
|
|
1355
|
+
[column]: {
|
|
1356
|
+
[operatorToWhereKey(operator)]: value,
|
|
1357
|
+
},
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Helper to create AND conditions
|
|
1362
|
+
*/
|
|
1363
|
+
function and(...conditions) {
|
|
1364
|
+
return { $and: conditions };
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Helper to create OR conditions
|
|
1368
|
+
*/
|
|
1369
|
+
function or(...conditions) {
|
|
1370
|
+
return { $or: conditions };
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Helper to create NOT conditions
|
|
1374
|
+
*/
|
|
1375
|
+
function not(condition) {
|
|
1376
|
+
return { $not: condition };
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Helper to create equality condition
|
|
1380
|
+
*/
|
|
1381
|
+
function eq(column, value) {
|
|
1382
|
+
return where(column, exports.Op.eq, value);
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Helper to create not equal condition
|
|
1386
|
+
*/
|
|
1387
|
+
function ne(column, value) {
|
|
1388
|
+
return where(column, exports.Op.ne, value);
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Helper to create greater than condition
|
|
1392
|
+
*/
|
|
1393
|
+
function gt(column, value) {
|
|
1394
|
+
return where(column, exports.Op.gt, value);
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Helper to create greater than or equal condition
|
|
1398
|
+
*/
|
|
1399
|
+
function gte(column, value) {
|
|
1400
|
+
return where(column, exports.Op.gte, value);
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Helper to create less than condition
|
|
1404
|
+
*/
|
|
1405
|
+
function lt(column, value) {
|
|
1406
|
+
return where(column, exports.Op.lt, value);
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Helper to create less than or equal condition
|
|
1410
|
+
*/
|
|
1411
|
+
function lte(column, value) {
|
|
1412
|
+
return where(column, exports.Op.lte, value);
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Helper to create LIKE condition
|
|
1416
|
+
* Wraps the value with % on both sides for contains matching
|
|
1417
|
+
*/
|
|
1418
|
+
function like(column, value) {
|
|
1419
|
+
return { [column]: { $like: `%${value}%` } };
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Helper to create NOT LIKE condition
|
|
1423
|
+
*/
|
|
1424
|
+
function notLike(column, value) {
|
|
1425
|
+
return { [column]: { $notLike: `%${value}%` } };
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Helper to create IN condition
|
|
1429
|
+
*/
|
|
1430
|
+
function inOp(column, values) {
|
|
1431
|
+
return where(column, exports.Op.in, values);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Helper to create NOT IN condition
|
|
1435
|
+
*/
|
|
1436
|
+
function notIn(column, values) {
|
|
1437
|
+
return where(column, exports.Op.notIn, values);
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Helper to create BETWEEN condition
|
|
1441
|
+
*/
|
|
1442
|
+
function between(column, start, end) {
|
|
1443
|
+
return where(column, exports.Op.between, [start, end]);
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Helper to create NOT BETWEEN condition
|
|
1447
|
+
*/
|
|
1448
|
+
function notBetween(column, start, end) {
|
|
1449
|
+
return where(column, exports.Op.notBetween, [start, end]);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Helper to create IS NULL condition
|
|
1453
|
+
*/
|
|
1454
|
+
function isNull(column) {
|
|
1455
|
+
return where(column, exports.Op.isNull, true);
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Helper to create IS NOT NULL condition
|
|
1459
|
+
*/
|
|
1460
|
+
function isNotNull(column) {
|
|
1461
|
+
return where(column, exports.Op.isNotNull, true);
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Helper to create NOT EXISTS condition for anti-joins
|
|
1465
|
+
* Returns records where the subquery returns no rows
|
|
1466
|
+
*
|
|
1467
|
+
* @example
|
|
1468
|
+
* // Find users with no orders
|
|
1469
|
+
* User.findAll({
|
|
1470
|
+
* where: notExists('SELECT 1 FROM orders WHERE orders.user_id = users.id')
|
|
1471
|
+
* })
|
|
1472
|
+
*/
|
|
1473
|
+
function notExists(subquery) {
|
|
1474
|
+
return { $notExists: typeof subquery === 'string' ? subquery : subquery };
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Helper to create EXISTS condition
|
|
1478
|
+
* Returns records where the subquery returns at least one row
|
|
1479
|
+
*
|
|
1480
|
+
* @example
|
|
1481
|
+
* // Find users with at least one order
|
|
1482
|
+
* User.findAll({
|
|
1483
|
+
* where: exists('SELECT 1 FROM orders WHERE orders.user_id = users.id')
|
|
1484
|
+
* })
|
|
1485
|
+
*/
|
|
1486
|
+
function exists(subquery) {
|
|
1487
|
+
return { $exists: typeof subquery === 'string' ? subquery : subquery };
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Helper to create NOT IN with subquery condition for anti-joins
|
|
1491
|
+
* Returns records where the column value is not in the subquery result
|
|
1492
|
+
*
|
|
1493
|
+
* @example
|
|
1494
|
+
* // Find users not in the banned users list
|
|
1495
|
+
* User.findAll({
|
|
1496
|
+
* where: notInSubquery('id', 'SELECT user_id FROM banned_users')
|
|
1497
|
+
* })
|
|
1498
|
+
*/
|
|
1499
|
+
function notInSubquery(column, subquery) {
|
|
1500
|
+
const subqueryValue = typeof subquery === 'string' ? subquery : subquery.$literal;
|
|
1501
|
+
return { [column]: { $notIn: subqueryValue } };
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Helper to create IN with subquery condition
|
|
1505
|
+
* Returns records where the column value is in the subquery result
|
|
1506
|
+
*
|
|
1507
|
+
* @example
|
|
1508
|
+
* // Find users that have placed orders
|
|
1509
|
+
* User.findAll({
|
|
1510
|
+
* where: inSubquery('id', 'SELECT user_id FROM orders')
|
|
1511
|
+
* })
|
|
1512
|
+
*/
|
|
1513
|
+
function inSubquery(column, subquery) {
|
|
1514
|
+
const subqueryValue = typeof subquery === 'string' ? subquery : subquery.$literal;
|
|
1515
|
+
return { [column]: { $in: subqueryValue } };
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Helper to create an anti-join condition using LEFT JOIN WHERE NULL pattern
|
|
1519
|
+
* This is useful for finding records that don't have related records
|
|
1520
|
+
*
|
|
1521
|
+
* @example
|
|
1522
|
+
* // Find users with no orders - using LEFT JOIN WHERE NULL pattern
|
|
1523
|
+
* User.findAll({
|
|
1524
|
+
* include: [{
|
|
1525
|
+
* model: Order,
|
|
1526
|
+
* where: { userId: { [Op.col]: 'User.id' } },
|
|
1527
|
+
* antiJoin: true
|
|
1528
|
+
* }]
|
|
1529
|
+
* })
|
|
1530
|
+
*/
|
|
1531
|
+
function antiJoin(model, options) {
|
|
1532
|
+
return {
|
|
1533
|
+
model,
|
|
1534
|
+
antiJoin: true,
|
|
1535
|
+
...options,
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Helper to create an exclude condition (alias for antiJoin)
|
|
1540
|
+
* Returns records that are NOT in the specified model/table
|
|
1541
|
+
*
|
|
1542
|
+
* @example
|
|
1543
|
+
* // Find users not in the banned users table
|
|
1544
|
+
* User.findAll({
|
|
1545
|
+
* include: [{ model: BannedUser, exclude: true }]
|
|
1546
|
+
* })
|
|
1547
|
+
*/
|
|
1548
|
+
function exclude(model, options) {
|
|
1549
|
+
return {
|
|
1550
|
+
model,
|
|
1551
|
+
exclude: true,
|
|
1552
|
+
...options,
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Helper to create ILIKE (case-insensitive LIKE) condition
|
|
1557
|
+
*/
|
|
1558
|
+
function iLike(column, value) {
|
|
1559
|
+
return { [column]: { $iLike: `%${value}%` } };
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Helper to create NOT ILIKE (case-insensitive NOT LIKE) condition
|
|
1563
|
+
*/
|
|
1564
|
+
function notILike(column, value) {
|
|
1565
|
+
return { [column]: { $notILike: `%${value}%` } };
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Helper to create REGEXP condition
|
|
1569
|
+
*/
|
|
1570
|
+
function regexp(column, pattern) {
|
|
1571
|
+
return where(column, exports.Op.regexp, pattern);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Helper to create NOT REGEXP condition
|
|
1575
|
+
*/
|
|
1576
|
+
function notRegexp(column, pattern) {
|
|
1577
|
+
return where(column, exports.Op.notRegexp, pattern);
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Helper to create IREGEXP (case-insensitive REGEXP) condition
|
|
1581
|
+
*/
|
|
1582
|
+
function iRegexp(column, pattern) {
|
|
1583
|
+
return where(column, exports.Op.iRegexp, pattern);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Helper to create NOT IREGEXP (case-insensitive NOT REGEXP) condition
|
|
1587
|
+
*/
|
|
1588
|
+
function notIRegexp(column, pattern) {
|
|
1589
|
+
return where(column, exports.Op.notIRegexp, pattern);
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Helper to create CONTAINS condition (for arrays/JSON)
|
|
1593
|
+
*/
|
|
1594
|
+
function contains(column, value) {
|
|
1595
|
+
return where(column, exports.Op.contains, value);
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Helper to create OVERLAP condition (for arrays)
|
|
1599
|
+
*/
|
|
1600
|
+
function overlap(column, value) {
|
|
1601
|
+
return where(column, exports.Op.overlap, value);
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Helper to create array contains condition
|
|
1605
|
+
* PostgreSQL: @> operator
|
|
1606
|
+
*
|
|
1607
|
+
* @example
|
|
1608
|
+
* ```typescript
|
|
1609
|
+
* // Find products where tags contains both 'electronics' and 'sale'
|
|
1610
|
+
* Product.findAll({ where: arrayContains('tags', ['electronics', 'sale']) })
|
|
1611
|
+
* // SQL: WHERE tags @> ARRAY['electronics', 'sale']
|
|
1612
|
+
* ```
|
|
1613
|
+
*/
|
|
1614
|
+
function arrayContains(column, value) {
|
|
1615
|
+
return where(column, exports.Op.arrayContains, value);
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Helper to create array contained by condition
|
|
1619
|
+
* PostgreSQL: <@ operator
|
|
1620
|
+
*
|
|
1621
|
+
* @example
|
|
1622
|
+
* ```typescript
|
|
1623
|
+
* // Find products where tags is contained by ['electronics', 'sale']
|
|
1624
|
+
* Product.findAll({ where: arrayContainedBy('tags', ['electronics', 'sale']) })
|
|
1625
|
+
* // SQL: WHERE tags <@ ARRAY['electronics', 'sale']
|
|
1626
|
+
* ```
|
|
1627
|
+
*/
|
|
1628
|
+
function arrayContainedBy(column, value) {
|
|
1629
|
+
return where(column, exports.Op.arrayContainedBy, value);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Helper to create array overlaps condition
|
|
1633
|
+
* PostgreSQL: && operator
|
|
1634
|
+
*
|
|
1635
|
+
* @example
|
|
1636
|
+
* ```typescript
|
|
1637
|
+
* // Find products where tags overlaps with ['electronics', 'sale']
|
|
1638
|
+
* Product.findAll({ where: arrayOverlaps('tags', ['electronics', 'sale']) })
|
|
1639
|
+
* // SQL: WHERE tags && ARRAY['electronics', 'sale']
|
|
1640
|
+
* ```
|
|
1641
|
+
*/
|
|
1642
|
+
function arrayOverlaps(column, value) {
|
|
1643
|
+
return where(column, exports.Op.arrayOverlaps, value);
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Helper to create array ANY condition
|
|
1647
|
+
* PostgreSQL: = ANY() operator
|
|
1648
|
+
*
|
|
1649
|
+
* @example
|
|
1650
|
+
* ```typescript
|
|
1651
|
+
* // Find products where any tag equals 'electronics'
|
|
1652
|
+
* Product.findAll({ where: arrayAny('tags', 'electronics') })
|
|
1653
|
+
* // SQL: WHERE 'electronics' = ANY(tags)
|
|
1654
|
+
* ```
|
|
1655
|
+
*/
|
|
1656
|
+
function arrayAny(column, value) {
|
|
1657
|
+
return where(column, exports.Op.arrayAny, value);
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Helper to create array ALL condition
|
|
1661
|
+
* PostgreSQL: ALL() operator
|
|
1662
|
+
*
|
|
1663
|
+
* @example
|
|
1664
|
+
* ```typescript
|
|
1665
|
+
* // Find products where all prices are greater than 100
|
|
1666
|
+
* Product.findAll({ where: arrayAll('prices', 100) })
|
|
1667
|
+
* // SQL: WHERE 100 > ALL(prices)
|
|
1668
|
+
* ```
|
|
1669
|
+
*/
|
|
1670
|
+
function arrayAll(column, value) {
|
|
1671
|
+
return where(column, exports.Op.arrayAll, value);
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Helper to create startsWith condition
|
|
1675
|
+
* Creates a LIKE 'value%' pattern
|
|
1676
|
+
*
|
|
1677
|
+
* @example
|
|
1678
|
+
* ```typescript
|
|
1679
|
+
* // Find users whose name starts with 'John'
|
|
1680
|
+
* User.findAll({ where: startsWith('name', 'John') })
|
|
1681
|
+
* // SQL: WHERE name LIKE 'John%'
|
|
1682
|
+
* ```
|
|
1683
|
+
*/
|
|
1684
|
+
function startsWith(column, value) {
|
|
1685
|
+
return { [column]: { $like: `${value}%` } };
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Helper to create notStartsWith condition
|
|
1689
|
+
* Creates a NOT LIKE 'value%' pattern
|
|
1690
|
+
*/
|
|
1691
|
+
function notStartsWith(column, value) {
|
|
1692
|
+
return { [column]: { $notLike: `${value}%` } };
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Helper to create endsWith condition
|
|
1696
|
+
* Creates a LIKE '%value' pattern
|
|
1697
|
+
*
|
|
1698
|
+
* @example
|
|
1699
|
+
* ```typescript
|
|
1700
|
+
* // Find users whose email ends with '@gmail.com'
|
|
1701
|
+
* User.findAll({ where: endsWith('email', '@gmail.com') })
|
|
1702
|
+
* // SQL: WHERE email LIKE '%@gmail.com'
|
|
1703
|
+
* ```
|
|
1704
|
+
*/
|
|
1705
|
+
function endsWith(column, value) {
|
|
1706
|
+
return { [column]: { $like: `%${value}` } };
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Helper to create notEndsWith condition
|
|
1710
|
+
* Creates a NOT LIKE '%value' pattern
|
|
1711
|
+
*/
|
|
1712
|
+
function notEndsWith(column, value) {
|
|
1713
|
+
return { [column]: { $notLike: `%${value}` } };
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Helper to create substring/contains condition
|
|
1717
|
+
* Creates a LIKE '%value%' pattern
|
|
1718
|
+
*
|
|
1719
|
+
* @example
|
|
1720
|
+
* ```typescript
|
|
1721
|
+
* // Find users whose name contains 'john'
|
|
1722
|
+
* User.findAll({ where: substring('name', 'john') })
|
|
1723
|
+
* // SQL: WHERE name LIKE '%john%'
|
|
1724
|
+
* ```
|
|
1725
|
+
*/
|
|
1726
|
+
function substring(column, value) {
|
|
1727
|
+
return { [column]: { $like: `%${value}%` } };
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Helper to create notSubstring condition
|
|
1731
|
+
* Creates a NOT LIKE '%value%' pattern
|
|
1732
|
+
*/
|
|
1733
|
+
function notSubstring(column, value) {
|
|
1734
|
+
return { [column]: { $notLike: `%${value}%` } };
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Helper to create ANY condition for array queries
|
|
1738
|
+
* PostgreSQL: column = ANY(array_column)
|
|
1739
|
+
*
|
|
1740
|
+
* @example
|
|
1741
|
+
* ```typescript
|
|
1742
|
+
* // Find users where role matches any value in the roles array
|
|
1743
|
+
* User.findAll({ where: { roles: { [Op.any]: ['admin', 'moderator'] } } })
|
|
1744
|
+
* ```
|
|
1745
|
+
*/
|
|
1746
|
+
function anyOp(column, values) {
|
|
1747
|
+
return where(column, exports.Op.any, values);
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Helper to create ALL condition for array queries
|
|
1751
|
+
* PostgreSQL: column = ALL(array_column)
|
|
1752
|
+
*
|
|
1753
|
+
* @example
|
|
1754
|
+
* ```typescript
|
|
1755
|
+
* // Find users where role matches all values in the roles array
|
|
1756
|
+
* User.findAll({ where: { roles: { [Op.all]: ['admin', 'moderator'] } } })
|
|
1757
|
+
* ```
|
|
1758
|
+
*/
|
|
1759
|
+
function allOp(column, values) {
|
|
1760
|
+
return where(column, exports.Op.all, values);
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Helper to create MATCH condition for full-text search
|
|
1764
|
+
* Database-specific: uses MATCH AGAINST in MySQL, @@ in PostgreSQL
|
|
1765
|
+
*
|
|
1766
|
+
* @example
|
|
1767
|
+
* ```typescript
|
|
1768
|
+
* // Full-text search
|
|
1769
|
+
* User.findAll({ where: match('bio', 'javascript programming') })
|
|
1770
|
+
* // MySQL: WHERE MATCH(bio) AGAINST('javascript programming')
|
|
1771
|
+
* // PostgreSQL: WHERE bio @@ to_tsquery('javascript')
|
|
1772
|
+
* ```
|
|
1773
|
+
*/
|
|
1774
|
+
function match(column, pattern) {
|
|
1775
|
+
return { [column]: { $match: pattern } };
|
|
1776
|
+
}
|
|
1777
|
+
// ==================== Order Helper Functions ====================
|
|
1778
|
+
/**
|
|
1779
|
+
* Sort direction for `ORDER BY` clauses.
|
|
1780
|
+
*
|
|
1781
|
+
* Companion enum for the string-literal union that `OrderExpression.direction`
|
|
1782
|
+
* (and other order-direction fields across the public API) accepts. Passing the raw
|
|
1783
|
+
* string literals `'ASC'` / `'DESC'` continues to work unchanged — the enum is purely
|
|
1784
|
+
* an additive, opt-in convenience. Re-exported here from `./types` so it can be
|
|
1785
|
+
* imported from either module.
|
|
1786
|
+
*
|
|
1787
|
+
* @example
|
|
1788
|
+
* ```typescript
|
|
1789
|
+
* // Using raw string literals (still supported)
|
|
1790
|
+
* User.findAll({ order: [['name', 'ASC']] });
|
|
1791
|
+
*
|
|
1792
|
+
* // Using the SortDirection enum
|
|
1793
|
+
* import { SortDirection } from 'prorm';
|
|
1794
|
+
* User.findAll({ order: [['name', SortDirection.ASC]] });
|
|
1795
|
+
* ```
|
|
1796
|
+
*/
|
|
1797
|
+
var types_1 = require("./types");
|
|
1798
|
+
Object.defineProperty(exports, "SortDirection", { enumerable: true, get: function () { return types_1.SortDirection; } });
|
|
1799
|
+
/**
|
|
1800
|
+
* Check if a value is an order expression
|
|
1801
|
+
*/
|
|
1802
|
+
function isOrderExpression(value) {
|
|
1803
|
+
return value && value.__type === 'order';
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Create an ascending order expression
|
|
1807
|
+
* SQL: ORDER BY field ASC
|
|
1808
|
+
*
|
|
1809
|
+
* @example
|
|
1810
|
+
* ```typescript
|
|
1811
|
+
* // Order by name ascending
|
|
1812
|
+
* User.findAll({ order: [asc('name')] })
|
|
1813
|
+
* // SQL: ORDER BY name ASC
|
|
1814
|
+
*
|
|
1815
|
+
* // Multiple order conditions
|
|
1816
|
+
* User.findAll({ order: [asc('name'), desc('createdAt')] })
|
|
1817
|
+
*
|
|
1818
|
+
* // Equivalent using the SortDirection enum directly (no helper function)
|
|
1819
|
+
* import { SortDirection } from 'prorm';
|
|
1820
|
+
* User.findAll({ order: [['name', SortDirection.ASC]] })
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
function asc(field) {
|
|
1824
|
+
return {
|
|
1825
|
+
__type: 'order',
|
|
1826
|
+
field,
|
|
1827
|
+
direction: 'ASC',
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Create a descending order expression
|
|
1832
|
+
* SQL: ORDER BY field DESC
|
|
1833
|
+
*
|
|
1834
|
+
* @example
|
|
1835
|
+
* ```typescript
|
|
1836
|
+
* // Order by createdAt descending (newest first)
|
|
1837
|
+
* User.findAll({ order: [desc('createdAt')] })
|
|
1838
|
+
* // SQL: ORDER BY createdAt DESC
|
|
1839
|
+
*
|
|
1840
|
+
* // Combined with asc
|
|
1841
|
+
* User.findAll({ order: [asc('name'), desc('createdAt')] })
|
|
1842
|
+
* ```
|
|
1843
|
+
*/
|
|
1844
|
+
function desc(field) {
|
|
1845
|
+
return {
|
|
1846
|
+
__type: 'order',
|
|
1847
|
+
field,
|
|
1848
|
+
direction: 'DESC',
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Create a random order expression
|
|
1853
|
+
* SQL: ORDER BY RANDOM() (SQLite/PostgreSQL) or ORDER BY RAND() (MySQL)
|
|
1854
|
+
*
|
|
1855
|
+
* @example
|
|
1856
|
+
* ```typescript
|
|
1857
|
+
* // Random ordering (useful for sampling)
|
|
1858
|
+
* User.findAll({ order: [random()] })
|
|
1859
|
+
* // SQLite: ORDER BY RANDOM()
|
|
1860
|
+
* // MySQL: ORDER BY RAND()
|
|
1861
|
+
* // PostgreSQL: ORDER BY RANDOM()
|
|
1862
|
+
*
|
|
1863
|
+
* // With limit for random sample
|
|
1864
|
+
* User.findAll({ order: [random()], limit: 5 })
|
|
1865
|
+
* ```
|
|
1866
|
+
*/
|
|
1867
|
+
function random() {
|
|
1868
|
+
return {
|
|
1869
|
+
__type: 'order',
|
|
1870
|
+
field: '',
|
|
1871
|
+
direction: 'RANDOM',
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Create a full-text search MATCH AGAINST condition (MySQL)
|
|
1876
|
+
* SQL: MATCH(columns) AGAINST(searchTerm [IN NATURAL LANGUAGE MODE | IN BOOLEAN MODE])
|
|
1877
|
+
*
|
|
1878
|
+
* @example
|
|
1879
|
+
* ```typescript
|
|
1880
|
+
* // Natural language mode search
|
|
1881
|
+
* import { matchAgainst } from 'orm';
|
|
1882
|
+
* Article.findAll({
|
|
1883
|
+
* where: {
|
|
1884
|
+
* [matchAgainst(['title', 'body'])]: 'database'
|
|
1885
|
+
* }
|
|
1886
|
+
* })
|
|
1887
|
+
* // SQL: WHERE MATCH(title, body) AGAINST('database' IN NATURAL LANGUAGE MODE)
|
|
1888
|
+
*
|
|
1889
|
+
* // Boolean mode search
|
|
1890
|
+
* Article.findAll({
|
|
1891
|
+
* where: {
|
|
1892
|
+
* [matchAgainst(['title', 'body'], { mode: 'boolean' })]: '+mysql -oracle'
|
|
1893
|
+
* }
|
|
1894
|
+
* })
|
|
1895
|
+
* // SQL: WHERE MATCH(title, body) AGAINST('+mysql -oracle' IN BOOLEAN MODE)
|
|
1896
|
+
* ```
|
|
1897
|
+
*/
|
|
1898
|
+
function matchAgainst(columns, options) {
|
|
1899
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
1900
|
+
return {
|
|
1901
|
+
$match: {
|
|
1902
|
+
columns: cols,
|
|
1903
|
+
mode: options?.mode || 'natural',
|
|
1904
|
+
},
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Create an explicit full-text search condition (alias for natural language mode)
|
|
1909
|
+
* SQL: MATCH(columns) AGAINST(searchTerm IN NATURAL LANGUAGE MODE)
|
|
1910
|
+
*
|
|
1911
|
+
* @example
|
|
1912
|
+
* ```typescript
|
|
1913
|
+
* import { matchFulltext } from 'orm';
|
|
1914
|
+
* Article.findAll({
|
|
1915
|
+
* where: {
|
|
1916
|
+
* [matchFulltext(['title', 'body'])]: 'database'
|
|
1917
|
+
* }
|
|
1918
|
+
* })
|
|
1919
|
+
* // SQL: WHERE MATCH(title, body) AGAINST('database' IN NATURAL LANGUAGE MODE)
|
|
1920
|
+
* ```
|
|
1921
|
+
*/
|
|
1922
|
+
function matchFulltext(columns) {
|
|
1923
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
1924
|
+
return {
|
|
1925
|
+
$match: {
|
|
1926
|
+
columns: cols,
|
|
1927
|
+
mode: 'natural',
|
|
1928
|
+
},
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Create a PostgreSQL to_tsvector expression
|
|
1933
|
+
* SQL: to_tsvector(config, column)
|
|
1934
|
+
*
|
|
1935
|
+
* @example
|
|
1936
|
+
* ```typescript
|
|
1937
|
+
* import { toTsvector } from 'orm';
|
|
1938
|
+
* // Using with to_tsquery
|
|
1939
|
+
* Article.findAll({
|
|
1940
|
+
* where: {
|
|
1941
|
+
* [toTsvector('title')]: { $tsquery: 'database' }
|
|
1942
|
+
* }
|
|
1943
|
+
* })
|
|
1944
|
+
* // SQL: WHERE to_tsvector('english', title) @@ to_tsquery('english', 'database')
|
|
1945
|
+
* ```
|
|
1946
|
+
*/
|
|
1947
|
+
function toTsvector(column, config) {
|
|
1948
|
+
return {
|
|
1949
|
+
$tsvector: {
|
|
1950
|
+
column,
|
|
1951
|
+
config: config || 'english',
|
|
1952
|
+
},
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Create a PostgreSQL to_tsquery expression
|
|
1957
|
+
* SQL: to_tsquery(config, query)
|
|
1958
|
+
*
|
|
1959
|
+
* @example
|
|
1960
|
+
* ```typescript
|
|
1961
|
+
* import { toTsquery } from 'orm';
|
|
1962
|
+
* Article.findAll({
|
|
1963
|
+
* where: {
|
|
1964
|
+
* body: { [toTsquery('database')]: true }
|
|
1965
|
+
* }
|
|
1966
|
+
* })
|
|
1967
|
+
* // SQL: WHERE body @@ to_tsquery('english', 'database')
|
|
1968
|
+
* ```
|
|
1969
|
+
*/
|
|
1970
|
+
function toTsquery(query, config) {
|
|
1971
|
+
return {
|
|
1972
|
+
$tsquery: {
|
|
1973
|
+
query,
|
|
1974
|
+
config: config || 'english',
|
|
1975
|
+
},
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Create a PostgreSQL plainto_tsquery expression (for phrase searching)
|
|
1980
|
+
* SQL: plainto_tsquery(config, query)
|
|
1981
|
+
*
|
|
1982
|
+
* @example
|
|
1983
|
+
* ```typescript
|
|
1984
|
+
* import { plainToTsquery } from 'orm';
|
|
1985
|
+
* Article.findAll({
|
|
1986
|
+
* where: {
|
|
1987
|
+
* body: { [plainToTsquery('database systems')]: true }
|
|
1988
|
+
* }
|
|
1989
|
+
* })
|
|
1990
|
+
* // SQL: WHERE body @@ plainto_tsquery('english', 'database systems')
|
|
1991
|
+
* ```
|
|
1992
|
+
*/
|
|
1993
|
+
function plainToTsquery(query, config) {
|
|
1994
|
+
return {
|
|
1995
|
+
$plainTsquery: {
|
|
1996
|
+
query,
|
|
1997
|
+
config: config || 'english',
|
|
1998
|
+
},
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Create a PostgreSQL phraseto_tsquery expression (for exact phrase matching)
|
|
2003
|
+
* SQL: phraseto_tsquery(config, query)
|
|
2004
|
+
*
|
|
2005
|
+
* @example
|
|
2006
|
+
* ```typescript
|
|
2007
|
+
* import { phraseToTsquery } from 'orm';
|
|
2008
|
+
* Article.findAll({
|
|
2009
|
+
* where: {
|
|
2010
|
+
* body: { [phraseToTsquery('database')]: true }
|
|
2011
|
+
* }
|
|
2012
|
+
* })
|
|
2013
|
+
* // SQL: WHERE body @@ phraseto_tsquery('english', 'database')
|
|
2014
|
+
* ```
|
|
2015
|
+
*/
|
|
2016
|
+
function phraseToTsquery(query, config) {
|
|
2017
|
+
return {
|
|
2018
|
+
$phraseTsquery: {
|
|
2019
|
+
query,
|
|
2020
|
+
config: config || 'english',
|
|
2021
|
+
},
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
// Default export
|
|
2025
|
+
exports.default = exports.Op;
|
|
2026
|
+
/**
|
|
2027
|
+
* Helper to create CONTAINED condition (for JSON/arrays)
|
|
2028
|
+
* Checks if the JSON column is contained in the specified value
|
|
2029
|
+
*/
|
|
2030
|
+
function contained(column, value) {
|
|
2031
|
+
return where(column, exports.Op.containedBy, value);
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Helper to create JSON key access condition
|
|
2035
|
+
* Access a specific key in a JSON column for comparison
|
|
2036
|
+
*/
|
|
2037
|
+
function jsonKey(column, path, value) {
|
|
2038
|
+
if (value !== undefined) {
|
|
2039
|
+
return {
|
|
2040
|
+
[column]: {
|
|
2041
|
+
$key: { path, value },
|
|
2042
|
+
},
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
return where(column, exports.Op.key, path);
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Helper to create JSON path extraction condition
|
|
2049
|
+
* Used for querying JSON/JSONB columns with path-based extraction
|
|
2050
|
+
*
|
|
2051
|
+
* @example
|
|
2052
|
+
* ```typescript
|
|
2053
|
+
* // Simple JSON path query
|
|
2054
|
+
* User.findAll({ where: json('settings', 'theme', 'dark') })
|
|
2055
|
+
* // SQL: WHERE json_extract(settings, '$.theme') = 'dark'
|
|
2056
|
+
*
|
|
2057
|
+
* // Nested path
|
|
2058
|
+
* User.findAll({ where: json('data', 'address.city', 'NYC') })
|
|
2059
|
+
* // SQL: WHERE json_extract(data, '$.address.city') = 'NYC'
|
|
2060
|
+
*
|
|
2061
|
+
* // With dot notation path (auto-prepended with $)
|
|
2062
|
+
* User.findAll({ where: json('preferences', 'notifications.enabled', true) })
|
|
2063
|
+
* // SQL: WHERE json_extract(preferences, '$.notifications.enabled') = true
|
|
2064
|
+
*
|
|
2065
|
+
* // Using with Op.json symbol directly in object
|
|
2066
|
+
* User.findAll({
|
|
2067
|
+
* where: {
|
|
2068
|
+
* settings: { [Op.json('theme')]: 'dark' }
|
|
2069
|
+
* }
|
|
2070
|
+
* })
|
|
2071
|
+
* // SQL: WHERE json_extract(settings, '$.theme') = 'dark'
|
|
2072
|
+
* ```
|
|
2073
|
+
*/
|
|
2074
|
+
function json(column, path, value) {
|
|
2075
|
+
// Normalize path: prepend $ if not already present
|
|
2076
|
+
const normalizedPath = path.startsWith('$.') ? path : `$.${path}`;
|
|
2077
|
+
if (value !== undefined) {
|
|
2078
|
+
return {
|
|
2079
|
+
[column]: {
|
|
2080
|
+
$json: { path: normalizedPath, value },
|
|
2081
|
+
},
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
// Just return the JSON extraction without comparison
|
|
2085
|
+
return {
|
|
2086
|
+
[column]: {
|
|
2087
|
+
$json: normalizedPath,
|
|
2088
|
+
},
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Helper to create JSON contains condition
|
|
2093
|
+
* Checks if JSON column contains a specific value or object
|
|
2094
|
+
*
|
|
2095
|
+
* @example
|
|
2096
|
+
* ```typescript
|
|
2097
|
+
* // Find users where preferences contains { theme: 'dark' }
|
|
2098
|
+
* User.findAll({ where: { preferences: { [Op.jsonContains]: { theme: 'dark' } } } })
|
|
2099
|
+
* // SQL (PostgreSQL): WHERE preferences @> '{"theme":"dark"}'
|
|
2100
|
+
* // SQL (MySQL): WHERE JSON_CONTAINS(preferences, '{"theme":"dark"}')
|
|
2101
|
+
*
|
|
2102
|
+
* // Check if JSON array contains a value
|
|
2103
|
+
* User.findAll({ where: { tags: { [Op.jsonContains]: 'admin' } } })
|
|
2104
|
+
* ```
|
|
2105
|
+
*/
|
|
2106
|
+
function jsonContains(column, value) {
|
|
2107
|
+
return {
|
|
2108
|
+
[column]: {
|
|
2109
|
+
$jsonContains: value,
|
|
2110
|
+
},
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Helper to create JSON has key condition
|
|
2115
|
+
* Checks if JSON column has a specific key
|
|
2116
|
+
*
|
|
2117
|
+
* @example
|
|
2118
|
+
* ```typescript
|
|
2119
|
+
* // Find users where data column has 'role' key
|
|
2120
|
+
* User.findAll({ where: { data: { [Op.jsonHasKey]: 'role' } } })
|
|
2121
|
+
* // SQL (PostgreSQL): WHERE data ? 'role'
|
|
2122
|
+
* // SQL (MySQL): WHERE JSON_CONTAINS_PATH(data, 'one', '$.role')
|
|
2123
|
+
*
|
|
2124
|
+
* // Nested key
|
|
2125
|
+
* User.findAll({ where: { data: { [Op.jsonHasKey]: 'settings.theme' } } })
|
|
2126
|
+
* ```
|
|
2127
|
+
*/
|
|
2128
|
+
function jsonHasKey(column, key) {
|
|
2129
|
+
return {
|
|
2130
|
+
[column]: {
|
|
2131
|
+
$jsonHasKey: key,
|
|
2132
|
+
},
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Helper to create JSONB concatenation condition
|
|
2137
|
+
* Concatenates two JSONB values (PostgreSQL || operator)
|
|
2138
|
+
*
|
|
2139
|
+
* @example
|
|
2140
|
+
* ```typescript
|
|
2141
|
+
* // Concatenate JSONB values
|
|
2142
|
+
* User.findAll({ where: jsonConcat('data', { key: 'value' }) })
|
|
2143
|
+
* // SQL: WHERE data || '{"key": "value"}'
|
|
2144
|
+
* ```
|
|
2145
|
+
*/
|
|
2146
|
+
function jsonConcat(column, jsonValue) {
|
|
2147
|
+
return {
|
|
2148
|
+
[column]: {
|
|
2149
|
+
$jsonConcat: jsonValue,
|
|
2150
|
+
},
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Helper to create JSONB delete key condition
|
|
2155
|
+
* Deletes a key from JSONB object (PostgreSQL - operator)
|
|
2156
|
+
*
|
|
2157
|
+
* @example
|
|
2158
|
+
* ```typescript
|
|
2159
|
+
* // Delete key from JSONB
|
|
2160
|
+
* User.findAll({ where: jsonDelete('data', 'key') })
|
|
2161
|
+
* // SQL: WHERE data - 'key'
|
|
2162
|
+
* ```
|
|
2163
|
+
*/
|
|
2164
|
+
function jsonDelete(column, key) {
|
|
2165
|
+
return {
|
|
2166
|
+
[column]: {
|
|
2167
|
+
$jsonDelete: key,
|
|
2168
|
+
},
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Helper to create JSONB delete by path condition
|
|
2173
|
+
* Deletes a key from JSONB by path (PostgreSQL #- operator)
|
|
2174
|
+
*
|
|
2175
|
+
* @example
|
|
2176
|
+
* ```typescript
|
|
2177
|
+
* // Delete by path
|
|
2178
|
+
* User.findAll({ where: jsonDeletePath('data', ['a', 'b']) })
|
|
2179
|
+
* // SQL: WHERE data #- '{a, b}'
|
|
2180
|
+
* ```
|
|
2181
|
+
*/
|
|
2182
|
+
function jsonDeletePath(column, path) {
|
|
2183
|
+
return {
|
|
2184
|
+
[column]: {
|
|
2185
|
+
$jsonDeletePath: path,
|
|
2186
|
+
},
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Helper to create JSON path exists condition
|
|
2191
|
+
* Checks if a JSON path exists and returns boolean (PostgreSQL @? operator)
|
|
2192
|
+
*
|
|
2193
|
+
* @example
|
|
2194
|
+
* ```typescript
|
|
2195
|
+
* // Check if path exists
|
|
2196
|
+
* User.findAll({ where: jsonPathExists('data', '$.key') })
|
|
2197
|
+
* // SQL: WHERE data @? '$.key'
|
|
2198
|
+
* ```
|
|
2199
|
+
*/
|
|
2200
|
+
function jsonPathExists(column, path) {
|
|
2201
|
+
return {
|
|
2202
|
+
[column]: {
|
|
2203
|
+
$jsonPathExists: path,
|
|
2204
|
+
},
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Helper to create JSON path query condition
|
|
2209
|
+
* Evaluates JSON path and returns result (PostgreSQL @@ operator)
|
|
2210
|
+
*
|
|
2211
|
+
* @example
|
|
2212
|
+
* ```typescript
|
|
2213
|
+
* // Query JSON path
|
|
2214
|
+
* User.findAll({ where: jsonPathQuery('data', '$.key') })
|
|
2215
|
+
* // SQL: WHERE data @@ '$.key'
|
|
2216
|
+
* ```
|
|
2217
|
+
*/
|
|
2218
|
+
function jsonPathQuery(column, path) {
|
|
2219
|
+
return {
|
|
2220
|
+
[column]: {
|
|
2221
|
+
$jsonPathQuery: path,
|
|
2222
|
+
},
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Create a SQL function expression
|
|
2227
|
+
* @param fnName - The SQL function name (e.g., 'COUNT', 'SUM', 'AVG', 'MAX', 'MIN')
|
|
2228
|
+
* @param args - Function arguments (can include col() expressions)
|
|
2229
|
+
*/
|
|
2230
|
+
function fn(fnName, ...args) {
|
|
2231
|
+
return {
|
|
2232
|
+
__type: 'fn',
|
|
2233
|
+
fn: fnName.toUpperCase(),
|
|
2234
|
+
args,
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Create a column reference for use in SQL functions
|
|
2239
|
+
* @param colName - The column name (supports 'table.column' format)
|
|
2240
|
+
*
|
|
2241
|
+
* @example
|
|
2242
|
+
* ```typescript
|
|
2243
|
+
* col('id') // -> "id"
|
|
2244
|
+
* col('user.id') // -> "user.id"
|
|
2245
|
+
* col('users.name') // -> "users.name"
|
|
2246
|
+
* ```
|
|
2247
|
+
*/
|
|
2248
|
+
function col(colName) {
|
|
2249
|
+
return {
|
|
2250
|
+
__type: 'col',
|
|
2251
|
+
col: colName,
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Create a literal SQL expression (inserted as-is into the query)
|
|
2256
|
+
* Returns { $literal: value } format for raw SQL expressions
|
|
2257
|
+
* Use with caution - this can lead to SQL injection if user input is included
|
|
2258
|
+
*
|
|
2259
|
+
* @example
|
|
2260
|
+
* ```typescript
|
|
2261
|
+
* // Use with update to set current timestamp
|
|
2262
|
+
* User.update({ lastLogin: literal('NOW()') }, { where: { ... } })
|
|
2263
|
+
*
|
|
2264
|
+
* // Use in where clause to compare with current time
|
|
2265
|
+
* User.findAll({
|
|
2266
|
+
* where: {
|
|
2267
|
+
* createdAt: { [Op.lt]: literal('NOW()') }
|
|
2268
|
+
* }
|
|
2269
|
+
* })
|
|
2270
|
+
*
|
|
2271
|
+
* // Compare column to a literal value
|
|
2272
|
+
* User.findAll({
|
|
2273
|
+
* where: {
|
|
2274
|
+
* updatedAt: { [Op.gt]: literal('createdAt') }
|
|
2275
|
+
* }
|
|
2276
|
+
* })
|
|
2277
|
+
* ```
|
|
2278
|
+
*/
|
|
2279
|
+
function literal(value) {
|
|
2280
|
+
return { $literal: value };
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Create a literal SQL expression (inserted as-is into the query)
|
|
2284
|
+
* Returns { __type: 'literal', sql: string } format
|
|
2285
|
+
* Use with caution - this can lead to SQL injection if user input is included
|
|
2286
|
+
*
|
|
2287
|
+
* @example
|
|
2288
|
+
* ```typescript
|
|
2289
|
+
* literalExpr('COUNT(*)') // -> { __type: 'literal', sql: 'COUNT(*)' }
|
|
2290
|
+
* literalExpr('NOW()') // -> { __type: 'literal', sql: 'NOW()' }
|
|
2291
|
+
* literalExpr('1 + 1') // -> { __type: 'literal', sql: '1 + 1' }
|
|
2292
|
+
* ```
|
|
2293
|
+
*/
|
|
2294
|
+
function literalExpr(sql) {
|
|
2295
|
+
return {
|
|
2296
|
+
__type: 'literal',
|
|
2297
|
+
sql,
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* Check if a value is a function expression
|
|
2302
|
+
*/
|
|
2303
|
+
function isFnExpression(value) {
|
|
2304
|
+
return value && value.__type === 'fn';
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Check if a value is a column expression
|
|
2308
|
+
*/
|
|
2309
|
+
function isColExpression(value) {
|
|
2310
|
+
return value && value.__type === 'col';
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Check if a value is a literal expression
|
|
2314
|
+
* Supports both { $literal: value } and { __type: 'literal', sql } formats
|
|
2315
|
+
*/
|
|
2316
|
+
function isLiteralExpression(value) {
|
|
2317
|
+
return value && (value.__type === 'literal' || '$literal' in value);
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Check if a value is a raw SQL literal ({ $literal: value } format)
|
|
2321
|
+
*/
|
|
2322
|
+
function isRawLiteral(value) {
|
|
2323
|
+
return (value !== null &&
|
|
2324
|
+
typeof value === 'object' &&
|
|
2325
|
+
'$literal' in value &&
|
|
2326
|
+
typeof value.$literal === 'string');
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Check if a value is a CAST expression
|
|
2330
|
+
*/
|
|
2331
|
+
function isCastExpression(value) {
|
|
2332
|
+
return value && value.__type === 'cast';
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Check if a value is an EXTRACT expression
|
|
2336
|
+
*/
|
|
2337
|
+
function isExtractExpression(value) {
|
|
2338
|
+
return value && value.__type === 'extract';
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Check if a value is a CONVERT expression
|
|
2342
|
+
*/
|
|
2343
|
+
function isConvExpression(value) {
|
|
2344
|
+
return value && value.__type === 'conv';
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Create a CAST expression for type casting
|
|
2348
|
+
* SQL: CAST(value AS type)
|
|
2349
|
+
*
|
|
2350
|
+
* @example
|
|
2351
|
+
* ```typescript
|
|
2352
|
+
* // CAST('2023-01-01' AS DATE)
|
|
2353
|
+
* User.findAll({
|
|
2354
|
+
* attributes: [[cast('2023-01-01', 'DATE'), 'dateOnly']]
|
|
2355
|
+
* })
|
|
2356
|
+
*
|
|
2357
|
+
* // CAST(column AS INTEGER)
|
|
2358
|
+
* User.findAll({
|
|
2359
|
+
* attributes: [[cast(col('amount'), 'INTEGER'), 'amountInt']]
|
|
2360
|
+
* })
|
|
2361
|
+
*
|
|
2362
|
+
* // CAST with different types
|
|
2363
|
+
* cast(col('price'), 'DECIMAL(10,2)')
|
|
2364
|
+
* cast(col('text'), 'CHAR(100)')
|
|
2365
|
+
* cast(col('json_data'), 'JSON')
|
|
2366
|
+
* ```
|
|
2367
|
+
*/
|
|
2368
|
+
function cast(value, type) {
|
|
2369
|
+
return {
|
|
2370
|
+
__type: 'cast',
|
|
2371
|
+
value,
|
|
2372
|
+
type: type.toUpperCase(),
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Type coercion helpers for common SQL types
|
|
2377
|
+
* Provides convenience methods for casting values to specific types
|
|
2378
|
+
*
|
|
2379
|
+
* @example
|
|
2380
|
+
* ```typescript
|
|
2381
|
+
* import { Cast, col } from 'orm';
|
|
2382
|
+
*
|
|
2383
|
+
* // Cast.string - CAST(value AS VARCHAR)
|
|
2384
|
+
* User.findAll({
|
|
2385
|
+
* attributes: [[Cast.string(col('age')), 'ageStr']]
|
|
2386
|
+
* })
|
|
2387
|
+
*
|
|
2388
|
+
* // Cast.integer - CAST(value AS INTEGER)
|
|
2389
|
+
* User.findAll({
|
|
2390
|
+
* attributes: [[Cast.integer(col('price')), 'priceInt']]
|
|
2391
|
+
* })
|
|
2392
|
+
*
|
|
2393
|
+
* // Cast.date - CAST(value AS DATE)
|
|
2394
|
+
* User.findAll({
|
|
2395
|
+
* attributes: [[Cast.date(col('timestamp')), 'dateOnly']]
|
|
2396
|
+
* })
|
|
2397
|
+
*
|
|
2398
|
+
* // Cast.boolean - CAST(value AS BOOLEAN)
|
|
2399
|
+
* User.findAll({
|
|
2400
|
+
* attributes: [[Cast.boolean(col('flag')), 'flagBool']]
|
|
2401
|
+
* })
|
|
2402
|
+
*
|
|
2403
|
+
* // Cast.float - CAST(value AS FLOAT)
|
|
2404
|
+
* User.findAll({
|
|
2405
|
+
* attributes: [[Cast.float(col('amount')), 'amountFloat']]
|
|
2406
|
+
* })
|
|
2407
|
+
*
|
|
2408
|
+
* // Cast.decimal - CAST(value AS DECIMAL)
|
|
2409
|
+
* User.findAll({
|
|
2410
|
+
* attributes: [[Cast.decimal(col('price'), 10, 2), 'priceDecimal']]
|
|
2411
|
+
* })
|
|
2412
|
+
*
|
|
2413
|
+
* // Cast.json - CAST(value AS JSON)
|
|
2414
|
+
* User.findAll({
|
|
2415
|
+
* attributes: [[Cast.json(col('data')), 'dataJson']]
|
|
2416
|
+
* })
|
|
2417
|
+
* ```
|
|
2418
|
+
*/
|
|
2419
|
+
exports.Cast = {
|
|
2420
|
+
/**
|
|
2421
|
+
* Cast to VARCHAR/string type
|
|
2422
|
+
*/
|
|
2423
|
+
string(value) {
|
|
2424
|
+
return cast(value, 'VARCHAR');
|
|
2425
|
+
},
|
|
2426
|
+
/**
|
|
2427
|
+
* Cast to CHAR type
|
|
2428
|
+
*/
|
|
2429
|
+
char(value, length) {
|
|
2430
|
+
return cast(value, length ? `CHAR(${length})` : 'CHAR');
|
|
2431
|
+
},
|
|
2432
|
+
/**
|
|
2433
|
+
* Cast to TEXT type
|
|
2434
|
+
*/
|
|
2435
|
+
text(value) {
|
|
2436
|
+
return cast(value, 'TEXT');
|
|
2437
|
+
},
|
|
2438
|
+
/**
|
|
2439
|
+
* Cast to INTEGER type
|
|
2440
|
+
*/
|
|
2441
|
+
integer(value) {
|
|
2442
|
+
return cast(value, 'INTEGER');
|
|
2443
|
+
},
|
|
2444
|
+
/**
|
|
2445
|
+
* Cast to BIGINT type
|
|
2446
|
+
*/
|
|
2447
|
+
bigint(value) {
|
|
2448
|
+
return cast(value, 'BIGINT');
|
|
2449
|
+
},
|
|
2450
|
+
/**
|
|
2451
|
+
* Cast to FLOAT type
|
|
2452
|
+
*/
|
|
2453
|
+
float(value) {
|
|
2454
|
+
return cast(value, 'FLOAT');
|
|
2455
|
+
},
|
|
2456
|
+
/**
|
|
2457
|
+
* Cast to DOUBLE type
|
|
2458
|
+
*/
|
|
2459
|
+
double(value) {
|
|
2460
|
+
return cast(value, 'DOUBLE');
|
|
2461
|
+
},
|
|
2462
|
+
/**
|
|
2463
|
+
* Cast to DECIMAL type
|
|
2464
|
+
*/
|
|
2465
|
+
decimal(value, precision, scale) {
|
|
2466
|
+
const type = precision ? `DECIMAL(${precision}${scale ? ', ' + scale : ''})` : 'DECIMAL';
|
|
2467
|
+
return cast(value, type);
|
|
2468
|
+
},
|
|
2469
|
+
/**
|
|
2470
|
+
* Cast to BOOLEAN type
|
|
2471
|
+
*/
|
|
2472
|
+
boolean(value) {
|
|
2473
|
+
return cast(value, 'BOOLEAN');
|
|
2474
|
+
},
|
|
2475
|
+
/**
|
|
2476
|
+
* Cast to DATE type
|
|
2477
|
+
*/
|
|
2478
|
+
date(value) {
|
|
2479
|
+
return cast(value, 'DATE');
|
|
2480
|
+
},
|
|
2481
|
+
/**
|
|
2482
|
+
* Cast to DATETIME type
|
|
2483
|
+
*/
|
|
2484
|
+
datetime(value) {
|
|
2485
|
+
return cast(value, 'DATETIME');
|
|
2486
|
+
},
|
|
2487
|
+
/**
|
|
2488
|
+
* Cast to TIME type
|
|
2489
|
+
*/
|
|
2490
|
+
time(value) {
|
|
2491
|
+
return cast(value, 'TIME');
|
|
2492
|
+
},
|
|
2493
|
+
/**
|
|
2494
|
+
* Cast to TIMESTAMP type
|
|
2495
|
+
*/
|
|
2496
|
+
timestamp(value) {
|
|
2497
|
+
return cast(value, 'TIMESTAMP');
|
|
2498
|
+
},
|
|
2499
|
+
/**
|
|
2500
|
+
* Cast to JSON type
|
|
2501
|
+
*/
|
|
2502
|
+
json(value) {
|
|
2503
|
+
return cast(value, 'JSON');
|
|
2504
|
+
},
|
|
2505
|
+
/**
|
|
2506
|
+
* Cast to BLOB type
|
|
2507
|
+
*/
|
|
2508
|
+
blob(value) {
|
|
2509
|
+
return cast(value, 'BLOB');
|
|
2510
|
+
},
|
|
2511
|
+
/**
|
|
2512
|
+
* Cast to UUID type
|
|
2513
|
+
*/
|
|
2514
|
+
uuid(value) {
|
|
2515
|
+
return cast(value, 'UUID');
|
|
2516
|
+
},
|
|
2517
|
+
/**
|
|
2518
|
+
* Cast to specific type
|
|
2519
|
+
*/
|
|
2520
|
+
valueOf(value, type) {
|
|
2521
|
+
return cast(value, type);
|
|
2522
|
+
},
|
|
2523
|
+
};
|
|
2524
|
+
/**
|
|
2525
|
+
* Create an EXTRACT expression for extracting date parts
|
|
2526
|
+
* SQL: EXTRACT(part FROM field)
|
|
2527
|
+
*
|
|
2528
|
+
* @example
|
|
2529
|
+
* ```typescript
|
|
2530
|
+
* // EXTRACT(YEAR FROM createdAt)
|
|
2531
|
+
* User.findAll({
|
|
2532
|
+
* attributes: [[extract('createdAt', 'year'), 'year']]
|
|
2533
|
+
* })
|
|
2534
|
+
*
|
|
2535
|
+
* // EXTRACT(MONTH FROM order_date)
|
|
2536
|
+
* User.findAll({
|
|
2537
|
+
* attributes: [[extract(col('order_date'), 'month'), 'month']]
|
|
2538
|
+
* })
|
|
2539
|
+
*
|
|
2540
|
+
* // Use in WHERE clause
|
|
2541
|
+
* User.findAll({
|
|
2542
|
+
* where: {
|
|
2543
|
+
* [extract('createdAt', 'year')]: 2024
|
|
2544
|
+
* }
|
|
2545
|
+
* })
|
|
2546
|
+
*
|
|
2547
|
+
* // Available parts: year, month, day, hour, minute, second
|
|
2548
|
+
* ```
|
|
2549
|
+
*/
|
|
2550
|
+
function extract(field, part) {
|
|
2551
|
+
return {
|
|
2552
|
+
__type: 'extract',
|
|
2553
|
+
field,
|
|
2554
|
+
part: part.toLowerCase(),
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* Create a CONVERT expression for type conversion
|
|
2559
|
+
* SQL: CONVERT(value, type) for MySQL, or CAST for other dialects
|
|
2560
|
+
*
|
|
2561
|
+
* @example
|
|
2562
|
+
* ```typescript
|
|
2563
|
+
* // CONVERT(value, to_type)
|
|
2564
|
+
* User.findAll({
|
|
2565
|
+
* attributes: [[conv(col('value'), null, 'CHAR'), 'strValue']]
|
|
2566
|
+
* })
|
|
2567
|
+
*
|
|
2568
|
+
* // CONVERT with charset (MySQL)
|
|
2569
|
+
* conv(col('name'), null, 'CHAR(255) CHARACTER SET utf8mb4')
|
|
2570
|
+
*
|
|
2571
|
+
* // Base type conversion
|
|
2572
|
+
* conv(col('text'), 'CHAR', 'INTEGER')
|
|
2573
|
+
* ```
|
|
2574
|
+
*/
|
|
2575
|
+
function conv(value, from, to) {
|
|
2576
|
+
return {
|
|
2577
|
+
__type: 'conv',
|
|
2578
|
+
value,
|
|
2579
|
+
from: from ?? null,
|
|
2580
|
+
to: to.toUpperCase(),
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
// ==================== Aggregate Function Helpers ====================
|
|
2584
|
+
/**
|
|
2585
|
+
* Create a SUM aggregate function expression
|
|
2586
|
+
* SQL: SUM(column)
|
|
2587
|
+
*
|
|
2588
|
+
* @example
|
|
2589
|
+
* ```typescript
|
|
2590
|
+
* // Sum all scores
|
|
2591
|
+
* User.findAll({
|
|
2592
|
+
* attributes: [[SUM(col('score')), 'totalScore']]
|
|
2593
|
+
* })
|
|
2594
|
+
*
|
|
2595
|
+
* // Sum with condition
|
|
2596
|
+
* User.findAll({
|
|
2597
|
+
* attributes: [[SUM(col('amount')), 'totalAmount']],
|
|
2598
|
+
* where: { status: 'completed' }
|
|
2599
|
+
* })
|
|
2600
|
+
* ```
|
|
2601
|
+
*/
|
|
2602
|
+
function SUM(column) {
|
|
2603
|
+
return fn('SUM', column);
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Create an AVG aggregate function expression
|
|
2607
|
+
* SQL: AVG(column)
|
|
2608
|
+
*
|
|
2609
|
+
* @example
|
|
2610
|
+
* ```typescript
|
|
2611
|
+
* // Average score
|
|
2612
|
+
* User.findAll({
|
|
2613
|
+
* attributes: [[AVG(col('score')), 'averageScore']]
|
|
2614
|
+
* })
|
|
2615
|
+
*
|
|
2616
|
+
* // Average with group by
|
|
2617
|
+
* User.findAll({
|
|
2618
|
+
* attributes: ['department', [AVG(col('salary')), 'avgSalary']],
|
|
2619
|
+
* group: ['department']
|
|
2620
|
+
* })
|
|
2621
|
+
* ```
|
|
2622
|
+
*/
|
|
2623
|
+
function AVG(column) {
|
|
2624
|
+
return fn('AVG', column);
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Create a COUNT aggregate function expression
|
|
2628
|
+
* SQL: COUNT(column)
|
|
2629
|
+
*
|
|
2630
|
+
* @example
|
|
2631
|
+
* ```typescript
|
|
2632
|
+
* // Count all records
|
|
2633
|
+
* User.findAll({
|
|
2634
|
+
* attributes: [[COUNT(col('id')), 'totalCount']]
|
|
2635
|
+
* })
|
|
2636
|
+
*
|
|
2637
|
+
* // Count distinct values
|
|
2638
|
+
* User.findAll({
|
|
2639
|
+
* attributes: [[COUNT(col('email')), 'uniqueEmails']]
|
|
2640
|
+
* })
|
|
2641
|
+
*
|
|
2642
|
+
* // Count with DISTINCT
|
|
2643
|
+
* User.findAll({
|
|
2644
|
+
* attributes: [[COUNT(fn('DISTINCT', col('role'))), 'uniqueRoles']]
|
|
2645
|
+
* })
|
|
2646
|
+
* ```
|
|
2647
|
+
*/
|
|
2648
|
+
function COUNT(column) {
|
|
2649
|
+
return fn('COUNT', column);
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Create a MAX aggregate function expression
|
|
2653
|
+
* SQL: MAX(column)
|
|
2654
|
+
*
|
|
2655
|
+
* @example
|
|
2656
|
+
* ```typescript
|
|
2657
|
+
* // Maximum score
|
|
2658
|
+
* User.findAll({
|
|
2659
|
+
* attributes: [[MAX(col('score')), 'highestScore']]
|
|
2660
|
+
* })
|
|
2661
|
+
*
|
|
2662
|
+
* // Latest date
|
|
2663
|
+
* User.findAll({
|
|
2664
|
+
* attributes: [[MAX(col('createdAt')), 'latestDate']]
|
|
2665
|
+
* })
|
|
2666
|
+
* ```
|
|
2667
|
+
*/
|
|
2668
|
+
function MAX(column) {
|
|
2669
|
+
return fn('MAX', column);
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Create a MIN aggregate function expression
|
|
2673
|
+
* SQL: MIN(column)
|
|
2674
|
+
*
|
|
2675
|
+
* @example
|
|
2676
|
+
* ```typescript
|
|
2677
|
+
* // Minimum score
|
|
2678
|
+
* User.findAll({
|
|
2679
|
+
* attributes: [[MIN(col('score')), 'lowestScore']]
|
|
2680
|
+
* })
|
|
2681
|
+
*
|
|
2682
|
+
* // Earliest date
|
|
2683
|
+
* User.findAll({
|
|
2684
|
+
* attributes: [[MIN(col('createdAt')), 'earliestDate']]
|
|
2685
|
+
* })
|
|
2686
|
+
* ```
|
|
2687
|
+
*/
|
|
2688
|
+
function MIN(column) {
|
|
2689
|
+
return fn('MIN', column);
|
|
2690
|
+
}
|