mongo.do 0.1.0 → 0.1.1
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/dist/cli/index.d.ts +1 -15
- package/dist/cli/index.js +4083 -196
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2740 -20
- package/dist/index.js +9375 -27
- package/dist/index.js.map +1 -1
- package/dist/worker-entrypoint-BEW23Gmp.d.ts +1331 -0
- package/dist/worker.d.ts +6 -5
- package/dist/worker.js +5477 -18
- package/dist/worker.js.map +1 -1
- package/package.json +3 -2
- package/dist/agentfs/adapters/anthropic.d.ts +0 -176
- package/dist/agentfs/adapters/anthropic.d.ts.map +0 -1
- package/dist/agentfs/adapters/anthropic.js +0 -629
- package/dist/agentfs/adapters/anthropic.js.map +0 -1
- package/dist/agentfs/adapters/index.d.ts +0 -21
- package/dist/agentfs/adapters/index.d.ts.map +0 -1
- package/dist/agentfs/adapters/index.js +0 -23
- package/dist/agentfs/adapters/index.js.map +0 -1
- package/dist/agentfs/adapters/vercel.d.ts +0 -260
- package/dist/agentfs/adapters/vercel.d.ts.map +0 -1
- package/dist/agentfs/adapters/vercel.js +0 -288
- package/dist/agentfs/adapters/vercel.js.map +0 -1
- package/dist/agentfs/glob.d.ts +0 -116
- package/dist/agentfs/glob.d.ts.map +0 -1
- package/dist/agentfs/glob.js +0 -270
- package/dist/agentfs/glob.js.map +0 -1
- package/dist/agentfs/grep.d.ts +0 -83
- package/dist/agentfs/grep.d.ts.map +0 -1
- package/dist/agentfs/grep.js +0 -193
- package/dist/agentfs/grep.js.map +0 -1
- package/dist/agentfs/index.d.ts +0 -22
- package/dist/agentfs/index.d.ts.map +0 -1
- package/dist/agentfs/index.js +0 -18
- package/dist/agentfs/index.js.map +0 -1
- package/dist/agentfs/kv-store.d.ts +0 -128
- package/dist/agentfs/kv-store.d.ts.map +0 -1
- package/dist/agentfs/kv-store.js +0 -227
- package/dist/agentfs/kv-store.js.map +0 -1
- package/dist/agentfs/mondo-agent.d.ts +0 -255
- package/dist/agentfs/mondo-agent.d.ts.map +0 -1
- package/dist/agentfs/mondo-agent.js +0 -879
- package/dist/agentfs/mondo-agent.js.map +0 -1
- package/dist/agentfs/toolcalls.d.ts +0 -130
- package/dist/agentfs/toolcalls.d.ts.map +0 -1
- package/dist/agentfs/toolcalls.js +0 -178
- package/dist/agentfs/toolcalls.js.map +0 -1
- package/dist/agentfs/types.d.ts +0 -171
- package/dist/agentfs/types.d.ts.map +0 -1
- package/dist/agentfs/types.js +0 -7
- package/dist/agentfs/types.js.map +0 -1
- package/dist/agentfs/vfs.d.ts +0 -249
- package/dist/agentfs/vfs.d.ts.map +0 -1
- package/dist/agentfs/vfs.js +0 -469
- package/dist/agentfs/vfs.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/mcp.d.ts +0 -119
- package/dist/cli/mcp.d.ts.map +0 -1
- package/dist/cli/mcp.js +0 -418
- package/dist/cli/mcp.js.map +0 -1
- package/dist/cli/server.d.ts +0 -179
- package/dist/cli/server.d.ts.map +0 -1
- package/dist/cli/server.js +0 -441
- package/dist/cli/server.js.map +0 -1
- package/dist/client/Collection.d.ts +0 -199
- package/dist/client/Collection.d.ts.map +0 -1
- package/dist/client/Collection.js +0 -256
- package/dist/client/Collection.js.map +0 -1
- package/dist/client/Database.d.ts +0 -68
- package/dist/client/Database.d.ts.map +0 -1
- package/dist/client/Database.js +0 -105
- package/dist/client/Database.js.map +0 -1
- package/dist/client/MongoClient.d.ts +0 -165
- package/dist/client/MongoClient.d.ts.map +0 -1
- package/dist/client/MongoClient.js +0 -307
- package/dist/client/MongoClient.js.map +0 -1
- package/dist/client/aggregation-cursor.d.ts +0 -210
- package/dist/client/aggregation-cursor.d.ts.map +0 -1
- package/dist/client/aggregation-cursor.js +0 -509
- package/dist/client/aggregation-cursor.js.map +0 -1
- package/dist/client/bulk-write.d.ts +0 -216
- package/dist/client/bulk-write.d.ts.map +0 -1
- package/dist/client/bulk-write.js +0 -63
- package/dist/client/bulk-write.js.map +0 -1
- package/dist/client/change-stream.d.ts +0 -245
- package/dist/client/change-stream.d.ts.map +0 -1
- package/dist/client/change-stream.js +0 -429
- package/dist/client/change-stream.js.map +0 -1
- package/dist/client/cursor.d.ts +0 -85
- package/dist/client/cursor.d.ts.map +0 -1
- package/dist/client/cursor.js +0 -156
- package/dist/client/cursor.js.map +0 -1
- package/dist/client/http-cursor.d.ts +0 -233
- package/dist/client/http-cursor.d.ts.map +0 -1
- package/dist/client/http-cursor.js +0 -496
- package/dist/client/http-cursor.js.map +0 -1
- package/dist/client/index.d.ts +0 -18
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -24
- package/dist/client/index.js.map +0 -1
- package/dist/client/mongo-client.d.ts +0 -60
- package/dist/client/mongo-client.d.ts.map +0 -1
- package/dist/client/mongo-client.js +0 -190
- package/dist/client/mongo-client.js.map +0 -1
- package/dist/client/mongo-collection.d.ts +0 -359
- package/dist/client/mongo-collection.d.ts.map +0 -1
- package/dist/client/mongo-collection.js +0 -1641
- package/dist/client/mongo-collection.js.map +0 -1
- package/dist/client/mongo-cursor.d.ts +0 -257
- package/dist/client/mongo-cursor.d.ts.map +0 -1
- package/dist/client/mongo-cursor.js +0 -621
- package/dist/client/mongo-cursor.js.map +0 -1
- package/dist/client/mongo-database.d.ts +0 -88
- package/dist/client/mongo-database.d.ts.map +0 -1
- package/dist/client/mongo-database.js +0 -139
- package/dist/client/mongo-database.js.map +0 -1
- package/dist/client/session.d.ts +0 -210
- package/dist/client/session.d.ts.map +0 -1
- package/dist/client/session.js +0 -326
- package/dist/client/session.js.map +0 -1
- package/dist/durable-object/index-manager.d.ts +0 -173
- package/dist/durable-object/index-manager.d.ts.map +0 -1
- package/dist/durable-object/index-manager.js +0 -764
- package/dist/durable-object/index-manager.js.map +0 -1
- package/dist/durable-object/index.d.ts +0 -12
- package/dist/durable-object/index.d.ts.map +0 -1
- package/dist/durable-object/index.js +0 -8
- package/dist/durable-object/index.js.map +0 -1
- package/dist/durable-object/mcp-handler.d.ts +0 -52
- package/dist/durable-object/mcp-handler.d.ts.map +0 -1
- package/dist/durable-object/mcp-handler.js +0 -186
- package/dist/durable-object/mcp-handler.js.map +0 -1
- package/dist/durable-object/migrations.d.ts +0 -40
- package/dist/durable-object/migrations.d.ts.map +0 -1
- package/dist/durable-object/migrations.js +0 -121
- package/dist/durable-object/migrations.js.map +0 -1
- package/dist/durable-object/mondo-database.d.ts +0 -148
- package/dist/durable-object/mondo-database.d.ts.map +0 -1
- package/dist/durable-object/mondo-database.js +0 -621
- package/dist/durable-object/mondo-database.js.map +0 -1
- package/dist/durable-object/schema.d.ts +0 -192
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js +0 -186
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/embedding/document-serializer.d.ts +0 -118
- package/dist/embedding/document-serializer.d.ts.map +0 -1
- package/dist/embedding/document-serializer.js +0 -339
- package/dist/embedding/document-serializer.js.map +0 -1
- package/dist/embedding/embedding-manager.d.ts +0 -136
- package/dist/embedding/embedding-manager.d.ts.map +0 -1
- package/dist/embedding/embedding-manager.js +0 -176
- package/dist/embedding/embedding-manager.js.map +0 -1
- package/dist/embedding/index.d.ts +0 -9
- package/dist/embedding/index.d.ts.map +0 -1
- package/dist/embedding/index.js +0 -9
- package/dist/embedding/index.js.map +0 -1
- package/dist/executor/aggregation-executor.d.ts +0 -93
- package/dist/executor/aggregation-executor.d.ts.map +0 -1
- package/dist/executor/aggregation-executor.js +0 -275
- package/dist/executor/aggregation-executor.js.map +0 -1
- package/dist/executor/function-executor.d.ts +0 -39
- package/dist/executor/function-executor.d.ts.map +0 -1
- package/dist/executor/function-executor.js +0 -168
- package/dist/executor/function-executor.js.map +0 -1
- package/dist/executor/index.d.ts +0 -4
- package/dist/executor/index.d.ts.map +0 -1
- package/dist/executor/index.js +0 -4
- package/dist/executor/index.js.map +0 -1
- package/dist/executor/vector-search-executor.d.ts +0 -71
- package/dist/executor/vector-search-executor.d.ts.map +0 -1
- package/dist/executor/vector-search-executor.js +0 -113
- package/dist/executor/vector-search-executor.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/mcp/adapters/anthropic-adapter.d.ts +0 -256
- package/dist/mcp/adapters/anthropic-adapter.d.ts.map +0 -1
- package/dist/mcp/adapters/anthropic-adapter.js +0 -409
- package/dist/mcp/adapters/anthropic-adapter.js.map +0 -1
- package/dist/mcp/adapters/base-adapter.d.ts +0 -164
- package/dist/mcp/adapters/base-adapter.d.ts.map +0 -1
- package/dist/mcp/adapters/base-adapter.js +0 -277
- package/dist/mcp/adapters/base-adapter.js.map +0 -1
- package/dist/mcp/adapters/errors.d.ts +0 -173
- package/dist/mcp/adapters/errors.d.ts.map +0 -1
- package/dist/mcp/adapters/errors.js +0 -305
- package/dist/mcp/adapters/errors.js.map +0 -1
- package/dist/mcp/adapters/index.d.ts +0 -65
- package/dist/mcp/adapters/index.d.ts.map +0 -1
- package/dist/mcp/adapters/index.js +0 -92
- package/dist/mcp/adapters/index.js.map +0 -1
- package/dist/mcp/adapters/streaming.d.ts +0 -200
- package/dist/mcp/adapters/streaming.d.ts.map +0 -1
- package/dist/mcp/adapters/streaming.js +0 -381
- package/dist/mcp/adapters/streaming.js.map +0 -1
- package/dist/mcp/adapters/vercel-adapter.d.ts +0 -321
- package/dist/mcp/adapters/vercel-adapter.d.ts.map +0 -1
- package/dist/mcp/adapters/vercel-adapter.js +0 -487
- package/dist/mcp/adapters/vercel-adapter.js.map +0 -1
- package/dist/mcp/agent.d.ts +0 -192
- package/dist/mcp/agent.d.ts.map +0 -1
- package/dist/mcp/agent.js +0 -338
- package/dist/mcp/agent.js.map +0 -1
- package/dist/mcp/cli.d.ts +0 -71
- package/dist/mcp/cli.d.ts.map +0 -1
- package/dist/mcp/cli.js +0 -218
- package/dist/mcp/cli.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -15
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -20
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/sandbox/database-proxy.d.ts +0 -118
- package/dist/mcp/sandbox/database-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/database-proxy.js +0 -154
- package/dist/mcp/sandbox/database-proxy.js.map +0 -1
- package/dist/mcp/sandbox/index.d.ts +0 -8
- package/dist/mcp/sandbox/index.d.ts.map +0 -1
- package/dist/mcp/sandbox/index.js +0 -7
- package/dist/mcp/sandbox/index.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -72
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -379
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -48
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -147
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox/worker-evaluator.d.ts +0 -160
- package/dist/mcp/sandbox/worker-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/worker-evaluator.js +0 -217
- package/dist/mcp/sandbox/worker-evaluator.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -75
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -278
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/tool-call-auditor.d.ts +0 -188
- package/dist/mcp/tool-call-auditor.d.ts.map +0 -1
- package/dist/mcp/tool-call-auditor.js +0 -198
- package/dist/mcp/tool-call-auditor.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -51
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -113
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools/fetch.d.ts +0 -43
- package/dist/mcp/tools/fetch.d.ts.map +0 -1
- package/dist/mcp/tools/fetch.js +0 -127
- package/dist/mcp/tools/fetch.js.map +0 -1
- package/dist/mcp/tools/index.d.ts +0 -9
- package/dist/mcp/tools/index.d.ts.map +0 -1
- package/dist/mcp/tools/index.js +0 -9
- package/dist/mcp/tools/index.js.map +0 -1
- package/dist/mcp/tools/search.d.ts +0 -60
- package/dist/mcp/tools/search.d.ts.map +0 -1
- package/dist/mcp/tools/search.js +0 -278
- package/dist/mcp/tools/search.js.map +0 -1
- package/dist/mcp/transport/http.d.ts +0 -144
- package/dist/mcp/transport/http.d.ts.map +0 -1
- package/dist/mcp/transport/http.js +0 -545
- package/dist/mcp/transport/http.js.map +0 -1
- package/dist/mcp/transport/index.d.ts +0 -10
- package/dist/mcp/transport/index.d.ts.map +0 -1
- package/dist/mcp/transport/index.js +0 -12
- package/dist/mcp/transport/index.js.map +0 -1
- package/dist/mcp/transport/stdio.d.ts +0 -132
- package/dist/mcp/transport/stdio.d.ts.map +0 -1
- package/dist/mcp/transport/stdio.js +0 -466
- package/dist/mcp/transport/stdio.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -476
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -178
- package/dist/mcp/types.js.map +0 -1
- package/dist/olap/cdc/cdc-buffer.d.ts +0 -92
- package/dist/olap/cdc/cdc-buffer.d.ts.map +0 -1
- package/dist/olap/cdc/cdc-buffer.js +0 -146
- package/dist/olap/cdc/cdc-buffer.js.map +0 -1
- package/dist/olap/cdc/cdc-emitter.d.ts +0 -118
- package/dist/olap/cdc/cdc-emitter.d.ts.map +0 -1
- package/dist/olap/cdc/cdc-emitter.js +0 -217
- package/dist/olap/cdc/cdc-emitter.js.map +0 -1
- package/dist/olap/cdc/cdc-schema.d.ts +0 -119
- package/dist/olap/cdc/cdc-schema.d.ts.map +0 -1
- package/dist/olap/cdc/cdc-schema.js +0 -253
- package/dist/olap/cdc/cdc-schema.js.map +0 -1
- package/dist/olap/cdc/index.d.ts +0 -10
- package/dist/olap/cdc/index.d.ts.map +0 -1
- package/dist/olap/cdc/index.js +0 -10
- package/dist/olap/cdc/index.js.map +0 -1
- package/dist/olap/clickhouse/iceberg.d.ts +0 -164
- package/dist/olap/clickhouse/iceberg.d.ts.map +0 -1
- package/dist/olap/clickhouse/iceberg.js +0 -138
- package/dist/olap/clickhouse/iceberg.js.map +0 -1
- package/dist/olap/clickhouse/index.d.ts +0 -14
- package/dist/olap/clickhouse/index.d.ts.map +0 -1
- package/dist/olap/clickhouse/index.js +0 -14
- package/dist/olap/clickhouse/index.js.map +0 -1
- package/dist/olap/clickhouse/mapper.d.ts +0 -170
- package/dist/olap/clickhouse/mapper.d.ts.map +0 -1
- package/dist/olap/clickhouse/mapper.js +0 -654
- package/dist/olap/clickhouse/mapper.js.map +0 -1
- package/dist/olap/clickhouse/olap-backend.d.ts +0 -181
- package/dist/olap/clickhouse/olap-backend.d.ts.map +0 -1
- package/dist/olap/clickhouse/olap-backend.js +0 -1083
- package/dist/olap/clickhouse/olap-backend.js.map +0 -1
- package/dist/olap/clickhouse/query-executor.d.ts +0 -163
- package/dist/olap/clickhouse/query-executor.d.ts.map +0 -1
- package/dist/olap/clickhouse/query-executor.js +0 -560
- package/dist/olap/clickhouse/query-executor.js.map +0 -1
- package/dist/olap/clickhouse/query.d.ts +0 -134
- package/dist/olap/clickhouse/query.d.ts.map +0 -1
- package/dist/olap/clickhouse/query.js +0 -512
- package/dist/olap/clickhouse/query.js.map +0 -1
- package/dist/olap/stage/index.d.ts +0 -6
- package/dist/olap/stage/index.d.ts.map +0 -1
- package/dist/olap/stage/index.js +0 -6
- package/dist/olap/stage/index.js.map +0 -1
- package/dist/olap/stage/parser.d.ts +0 -68
- package/dist/olap/stage/parser.d.ts.map +0 -1
- package/dist/olap/stage/parser.js +0 -293
- package/dist/olap/stage/parser.js.map +0 -1
- package/dist/olap/stage/router.d.ts +0 -94
- package/dist/olap/stage/router.d.ts.map +0 -1
- package/dist/olap/stage/router.js +0 -390
- package/dist/olap/stage/router.js.map +0 -1
- package/dist/rpc/endpoint.d.ts +0 -52
- package/dist/rpc/endpoint.d.ts.map +0 -1
- package/dist/rpc/endpoint.js +0 -734
- package/dist/rpc/endpoint.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -34
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -45
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/rpc-client.d.ts +0 -275
- package/dist/rpc/rpc-client.d.ts.map +0 -1
- package/dist/rpc/rpc-client.js +0 -735
- package/dist/rpc/rpc-client.js.map +0 -1
- package/dist/rpc/rpc-target.d.ts +0 -220
- package/dist/rpc/rpc-target.d.ts.map +0 -1
- package/dist/rpc/rpc-target.js +0 -500
- package/dist/rpc/rpc-target.js.map +0 -1
- package/dist/rpc/worker-entrypoint.d.ts +0 -159
- package/dist/rpc/worker-entrypoint.d.ts.map +0 -1
- package/dist/rpc/worker-entrypoint.js +0 -212
- package/dist/rpc/worker-entrypoint.js.map +0 -1
- package/dist/server.d.ts +0 -18
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -129
- package/dist/server.js.map +0 -1
- package/dist/studio/components/browser/CollectionItem.d.ts +0 -26
- package/dist/studio/components/browser/CollectionItem.d.ts.map +0 -1
- package/dist/studio/components/browser/CollectionItem.js +0 -143
- package/dist/studio/components/browser/CollectionItem.js.map +0 -1
- package/dist/studio/components/browser/CollectionTree.d.ts +0 -45
- package/dist/studio/components/browser/CollectionTree.d.ts.map +0 -1
- package/dist/studio/components/browser/CollectionTree.js +0 -207
- package/dist/studio/components/browser/CollectionTree.js.map +0 -1
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.d.ts +0 -51
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.d.ts.map +0 -1
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.js +0 -185
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.js.map +0 -1
- package/dist/studio/components/browser/DatabaseBrowser.d.ts +0 -46
- package/dist/studio/components/browser/DatabaseBrowser.d.ts.map +0 -1
- package/dist/studio/components/browser/DatabaseBrowser.js +0 -304
- package/dist/studio/components/browser/DatabaseBrowser.js.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionItem.test.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/CollectionItem.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionItem.test.js +0 -169
- package/dist/studio/components/browser/__tests__/CollectionItem.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionTree.test.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/CollectionTree.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionTree.test.js +0 -203
- package/dist/studio/components/browser/__tests__/CollectionTree.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.d.ts +0 -8
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.js +0 -522
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.js +0 -518
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/setup.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/setup.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/setup.js +0 -22
- package/dist/studio/components/browser/__tests__/setup.js.map +0 -1
- package/dist/studio/components/browser/index.d.ts +0 -15
- package/dist/studio/components/browser/index.d.ts.map +0 -1
- package/dist/studio/components/browser/index.js +0 -10
- package/dist/studio/components/browser/index.js.map +0 -1
- package/dist/studio/components/browser/types.d.ts +0 -33
- package/dist/studio/components/browser/types.d.ts.map +0 -1
- package/dist/studio/components/browser/types.js +0 -5
- package/dist/studio/components/browser/types.js.map +0 -1
- package/dist/studio/components/connection/ConnectionForm.d.ts +0 -59
- package/dist/studio/components/connection/ConnectionForm.d.ts.map +0 -1
- package/dist/studio/components/connection/ConnectionForm.js +0 -274
- package/dist/studio/components/connection/ConnectionForm.js.map +0 -1
- package/dist/studio/components/connection/ConnectionList.d.ts +0 -59
- package/dist/studio/components/connection/ConnectionList.d.ts.map +0 -1
- package/dist/studio/components/connection/ConnectionList.js +0 -286
- package/dist/studio/components/connection/ConnectionList.js.map +0 -1
- package/dist/studio/components/connection/ConnectionPanel.d.ts +0 -132
- package/dist/studio/components/connection/ConnectionPanel.d.ts.map +0 -1
- package/dist/studio/components/connection/ConnectionPanel.js +0 -293
- package/dist/studio/components/connection/ConnectionPanel.js.map +0 -1
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.d.ts +0 -8
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.d.ts.map +0 -1
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.js +0 -632
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.js.map +0 -1
- package/dist/studio/components/connection/__tests__/setup.d.ts +0 -5
- package/dist/studio/components/connection/__tests__/setup.d.ts.map +0 -1
- package/dist/studio/components/connection/__tests__/setup.js +0 -11
- package/dist/studio/components/connection/__tests__/setup.js.map +0 -1
- package/dist/studio/components/connection/index.d.ts +0 -10
- package/dist/studio/components/connection/index.d.ts.map +0 -1
- package/dist/studio/components/connection/index.js +0 -7
- package/dist/studio/components/connection/index.js.map +0 -1
- package/dist/studio/components/crud/DeleteDocumentDialog.d.ts +0 -91
- package/dist/studio/components/crud/DeleteDocumentDialog.d.ts.map +0 -1
- package/dist/studio/components/crud/DeleteDocumentDialog.js +0 -273
- package/dist/studio/components/crud/DeleteDocumentDialog.js.map +0 -1
- package/dist/studio/components/crud/DocumentEditor.d.ts +0 -32
- package/dist/studio/components/crud/DocumentEditor.d.ts.map +0 -1
- package/dist/studio/components/crud/DocumentEditor.js +0 -546
- package/dist/studio/components/crud/DocumentEditor.js.map +0 -1
- package/dist/studio/components/crud/InsertDocumentDialog.d.ts +0 -78
- package/dist/studio/components/crud/InsertDocumentDialog.d.ts.map +0 -1
- package/dist/studio/components/crud/InsertDocumentDialog.js +0 -323
- package/dist/studio/components/crud/InsertDocumentDialog.js.map +0 -1
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.d.ts +0 -5
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.js +0 -298
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.js.map +0 -1
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.d.ts +0 -8
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.js +0 -368
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.js.map +0 -1
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.d.ts +0 -2
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.js +0 -352
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.js.map +0 -1
- package/dist/studio/components/crud/__tests__/setup.d.ts +0 -5
- package/dist/studio/components/crud/__tests__/setup.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/setup.js +0 -22
- package/dist/studio/components/crud/__tests__/setup.js.map +0 -1
- package/dist/studio/components/crud/index.d.ts +0 -12
- package/dist/studio/components/crud/index.d.ts.map +0 -1
- package/dist/studio/components/crud/index.js +0 -11
- package/dist/studio/components/crud/index.js.map +0 -1
- package/dist/studio/hooks/useConnection.d.ts +0 -127
- package/dist/studio/hooks/useConnection.d.ts.map +0 -1
- package/dist/studio/hooks/useConnection.js +0 -414
- package/dist/studio/hooks/useConnection.js.map +0 -1
- package/dist/studio/hooks/useDatabaseBrowser.d.ts +0 -107
- package/dist/studio/hooks/useDatabaseBrowser.d.ts.map +0 -1
- package/dist/studio/hooks/useDatabaseBrowser.js +0 -294
- package/dist/studio/hooks/useDatabaseBrowser.js.map +0 -1
- package/dist/studio/index.d.ts +0 -16
- package/dist/studio/index.d.ts.map +0 -1
- package/dist/studio/index.js +0 -14
- package/dist/studio/index.js.map +0 -1
- package/dist/studio/types/connection.d.ts +0 -266
- package/dist/studio/types/connection.d.ts.map +0 -1
- package/dist/studio/types/connection.js +0 -159
- package/dist/studio/types/connection.js.map +0 -1
- package/dist/studio/vitest.config.d.ts +0 -3
- package/dist/studio/vitest.config.d.ts.map +0 -1
- package/dist/studio/vitest.config.js +0 -33
- package/dist/studio/vitest.config.js.map +0 -1
- package/dist/translator/aggregation-translator.d.ts +0 -51
- package/dist/translator/aggregation-translator.d.ts.map +0 -1
- package/dist/translator/aggregation-translator.js +0 -324
- package/dist/translator/aggregation-translator.js.map +0 -1
- package/dist/translator/dialect.d.ts +0 -131
- package/dist/translator/dialect.d.ts.map +0 -1
- package/dist/translator/dialect.js +0 -276
- package/dist/translator/dialect.js.map +0 -1
- package/dist/translator/geo-translator.d.ts +0 -91
- package/dist/translator/geo-translator.d.ts.map +0 -1
- package/dist/translator/geo-translator.js +0 -587
- package/dist/translator/geo-translator.js.map +0 -1
- package/dist/translator/hybrid-translator.d.ts +0 -70
- package/dist/translator/hybrid-translator.d.ts.map +0 -1
- package/dist/translator/hybrid-translator.js +0 -193
- package/dist/translator/hybrid-translator.js.map +0 -1
- package/dist/translator/index.d.ts +0 -13
- package/dist/translator/index.d.ts.map +0 -1
- package/dist/translator/index.js +0 -11
- package/dist/translator/index.js.map +0 -1
- package/dist/translator/query-translator.d.ts +0 -211
- package/dist/translator/query-translator.d.ts.map +0 -1
- package/dist/translator/query-translator.js +0 -1276
- package/dist/translator/query-translator.js.map +0 -1
- package/dist/translator/search-highlight.d.ts +0 -83
- package/dist/translator/search-highlight.d.ts.map +0 -1
- package/dist/translator/search-highlight.js +0 -83
- package/dist/translator/search-highlight.js.map +0 -1
- package/dist/translator/search-translator.d.ts +0 -155
- package/dist/translator/search-translator.d.ts.map +0 -1
- package/dist/translator/search-translator.js +0 -241
- package/dist/translator/search-translator.js.map +0 -1
- package/dist/translator/stages/add-fields-stage.d.ts +0 -7
- package/dist/translator/stages/add-fields-stage.d.ts.map +0 -1
- package/dist/translator/stages/add-fields-stage.js +0 -72
- package/dist/translator/stages/add-fields-stage.js.map +0 -1
- package/dist/translator/stages/bucket-stage.d.ts +0 -7
- package/dist/translator/stages/bucket-stage.d.ts.map +0 -1
- package/dist/translator/stages/bucket-stage.js +0 -87
- package/dist/translator/stages/bucket-stage.js.map +0 -1
- package/dist/translator/stages/count-stage.d.ts +0 -7
- package/dist/translator/stages/count-stage.d.ts.map +0 -1
- package/dist/translator/stages/count-stage.js +0 -12
- package/dist/translator/stages/count-stage.js.map +0 -1
- package/dist/translator/stages/expression-translator.d.ts +0 -68
- package/dist/translator/stages/expression-translator.d.ts.map +0 -1
- package/dist/translator/stages/expression-translator.js +0 -467
- package/dist/translator/stages/expression-translator.js.map +0 -1
- package/dist/translator/stages/facet-stage.d.ts +0 -13
- package/dist/translator/stages/facet-stage.d.ts.map +0 -1
- package/dist/translator/stages/facet-stage.js +0 -26
- package/dist/translator/stages/facet-stage.js.map +0 -1
- package/dist/translator/stages/fusion-stages.d.ts +0 -118
- package/dist/translator/stages/fusion-stages.d.ts.map +0 -1
- package/dist/translator/stages/fusion-stages.js +0 -201
- package/dist/translator/stages/fusion-stages.js.map +0 -1
- package/dist/translator/stages/group-stage.d.ts +0 -8
- package/dist/translator/stages/group-stage.d.ts.map +0 -1
- package/dist/translator/stages/group-stage.js +0 -123
- package/dist/translator/stages/group-stage.js.map +0 -1
- package/dist/translator/stages/index.d.ts +0 -24
- package/dist/translator/stages/index.d.ts.map +0 -1
- package/dist/translator/stages/index.js +0 -24
- package/dist/translator/stages/index.js.map +0 -1
- package/dist/translator/stages/join-optimizer.d.ts +0 -37
- package/dist/translator/stages/join-optimizer.d.ts.map +0 -1
- package/dist/translator/stages/join-optimizer.js +0 -93
- package/dist/translator/stages/join-optimizer.js.map +0 -1
- package/dist/translator/stages/limit-stage.d.ts +0 -7
- package/dist/translator/stages/limit-stage.d.ts.map +0 -1
- package/dist/translator/stages/limit-stage.js +0 -11
- package/dist/translator/stages/limit-stage.js.map +0 -1
- package/dist/translator/stages/lookup-stage.d.ts +0 -7
- package/dist/translator/stages/lookup-stage.d.ts.map +0 -1
- package/dist/translator/stages/lookup-stage.js +0 -73
- package/dist/translator/stages/lookup-stage.js.map +0 -1
- package/dist/translator/stages/match-stage.d.ts +0 -7
- package/dist/translator/stages/match-stage.d.ts.map +0 -1
- package/dist/translator/stages/match-stage.js +0 -14
- package/dist/translator/stages/match-stage.js.map +0 -1
- package/dist/translator/stages/optimizer.d.ts +0 -15
- package/dist/translator/stages/optimizer.d.ts.map +0 -1
- package/dist/translator/stages/optimizer.js +0 -249
- package/dist/translator/stages/optimizer.js.map +0 -1
- package/dist/translator/stages/parallel-facet.d.ts +0 -47
- package/dist/translator/stages/parallel-facet.d.ts.map +0 -1
- package/dist/translator/stages/parallel-facet.js +0 -57
- package/dist/translator/stages/parallel-facet.js.map +0 -1
- package/dist/translator/stages/project-stage.d.ts +0 -8
- package/dist/translator/stages/project-stage.d.ts.map +0 -1
- package/dist/translator/stages/project-stage.js +0 -145
- package/dist/translator/stages/project-stage.js.map +0 -1
- package/dist/translator/stages/search-stage.d.ts +0 -60
- package/dist/translator/stages/search-stage.d.ts.map +0 -1
- package/dist/translator/stages/search-stage.js +0 -89
- package/dist/translator/stages/search-stage.js.map +0 -1
- package/dist/translator/stages/skip-stage.d.ts +0 -7
- package/dist/translator/stages/skip-stage.d.ts.map +0 -1
- package/dist/translator/stages/skip-stage.js +0 -11
- package/dist/translator/stages/skip-stage.js.map +0 -1
- package/dist/translator/stages/sort-stage.d.ts +0 -7
- package/dist/translator/stages/sort-stage.d.ts.map +0 -1
- package/dist/translator/stages/sort-stage.js +0 -21
- package/dist/translator/stages/sort-stage.js.map +0 -1
- package/dist/translator/stages/types.d.ts +0 -136
- package/dist/translator/stages/types.d.ts.map +0 -1
- package/dist/translator/stages/types.js +0 -5
- package/dist/translator/stages/types.js.map +0 -1
- package/dist/translator/stages/unwind-stage.d.ts +0 -7
- package/dist/translator/stages/unwind-stage.d.ts.map +0 -1
- package/dist/translator/stages/unwind-stage.js +0 -61
- package/dist/translator/stages/unwind-stage.js.map +0 -1
- package/dist/translator/stages/vector-search-stage.d.ts +0 -53
- package/dist/translator/stages/vector-search-stage.d.ts.map +0 -1
- package/dist/translator/stages/vector-search-stage.js +0 -62
- package/dist/translator/stages/vector-search-stage.js.map +0 -1
- package/dist/translator/update-translator.d.ts +0 -148
- package/dist/translator/update-translator.d.ts.map +0 -1
- package/dist/translator/update-translator.js +0 -819
- package/dist/translator/update-translator.js.map +0 -1
- package/dist/translator/vector-translator.d.ts +0 -89
- package/dist/translator/vector-translator.d.ts.map +0 -1
- package/dist/translator/vector-translator.js +0 -106
- package/dist/translator/vector-translator.js.map +0 -1
- package/dist/types/env.d.ts +0 -31
- package/dist/types/env.d.ts.map +0 -1
- package/dist/types/env.js +0 -5
- package/dist/types/env.js.map +0 -1
- package/dist/types/function.d.ts +0 -65
- package/dist/types/function.d.ts.map +0 -1
- package/dist/types/function.js +0 -5
- package/dist/types/function.js.map +0 -1
- package/dist/types/index.d.ts +0 -137
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -13
- package/dist/types/index.js.map +0 -1
- package/dist/types/mongodb.d.ts +0 -258
- package/dist/types/mongodb.d.ts.map +0 -1
- package/dist/types/mongodb.js +0 -5
- package/dist/types/mongodb.js.map +0 -1
- package/dist/types/objectid.d.ts +0 -130
- package/dist/types/objectid.d.ts.map +0 -1
- package/dist/types/objectid.js +0 -314
- package/dist/types/objectid.js.map +0 -1
- package/dist/types/rpc.d.ts +0 -313
- package/dist/types/rpc.d.ts.map +0 -1
- package/dist/types/rpc.js +0 -136
- package/dist/types/rpc.js.map +0 -1
- package/dist/types/vectorize.d.ts +0 -136
- package/dist/types/vectorize.d.ts.map +0 -1
- package/dist/types/vectorize.js +0 -8
- package/dist/types/vectorize.js.map +0 -1
- package/dist/utils/sql-safety.d.ts +0 -64
- package/dist/utils/sql-safety.d.ts.map +0 -1
- package/dist/utils/sql-safety.js +0 -112
- package/dist/utils/sql-safety.js.map +0 -1
- package/dist/validation/document-validator.d.ts +0 -195
- package/dist/validation/document-validator.d.ts.map +0 -1
- package/dist/validation/document-validator.js +0 -529
- package/dist/validation/document-validator.js.map +0 -1
- package/dist/vectorize/document-serializer.d.ts +0 -119
- package/dist/vectorize/document-serializer.d.ts.map +0 -1
- package/dist/vectorize/document-serializer.js +0 -320
- package/dist/vectorize/document-serializer.js.map +0 -1
- package/dist/wire/auth/index.d.ts +0 -5
- package/dist/wire/auth/index.d.ts.map +0 -1
- package/dist/wire/auth/index.js +0 -5
- package/dist/wire/auth/index.js.map +0 -1
- package/dist/wire/auth/scram.d.ts +0 -160
- package/dist/wire/auth/scram.d.ts.map +0 -1
- package/dist/wire/auth/scram.js +0 -425
- package/dist/wire/auth/scram.js.map +0 -1
- package/dist/wire/backend/interface.d.ts +0 -168
- package/dist/wire/backend/interface.d.ts.map +0 -1
- package/dist/wire/backend/interface.js +0 -10
- package/dist/wire/backend/interface.js.map +0 -1
- package/dist/wire/backend/local-sqlite.d.ts +0 -89
- package/dist/wire/backend/local-sqlite.d.ts.map +0 -1
- package/dist/wire/backend/local-sqlite.js +0 -1002
- package/dist/wire/backend/local-sqlite.js.map +0 -1
- package/dist/wire/backend/query-router.d.ts +0 -197
- package/dist/wire/backend/query-router.d.ts.map +0 -1
- package/dist/wire/backend/query-router.js +0 -590
- package/dist/wire/backend/query-router.js.map +0 -1
- package/dist/wire/backend/validation.d.ts +0 -26
- package/dist/wire/backend/validation.d.ts.map +0 -1
- package/dist/wire/backend/validation.js +0 -79
- package/dist/wire/backend/validation.js.map +0 -1
- package/dist/wire/backend/workers-proxy.d.ts +0 -95
- package/dist/wire/backend/workers-proxy.d.ts.map +0 -1
- package/dist/wire/backend/workers-proxy.js +0 -429
- package/dist/wire/backend/workers-proxy.js.map +0 -1
- package/dist/wire/commands/admin.d.ts +0 -49
- package/dist/wire/commands/admin.d.ts.map +0 -1
- package/dist/wire/commands/admin.js +0 -272
- package/dist/wire/commands/admin.js.map +0 -1
- package/dist/wire/commands/aggregate.d.ts +0 -15
- package/dist/wire/commands/aggregate.d.ts.map +0 -1
- package/dist/wire/commands/aggregate.js +0 -98
- package/dist/wire/commands/aggregate.js.map +0 -1
- package/dist/wire/commands/auth.d.ts +0 -58
- package/dist/wire/commands/auth.d.ts.map +0 -1
- package/dist/wire/commands/auth.js +0 -158
- package/dist/wire/commands/auth.js.map +0 -1
- package/dist/wire/commands/crud.d.ts +0 -49
- package/dist/wire/commands/crud.d.ts.map +0 -1
- package/dist/wire/commands/crud.js +0 -336
- package/dist/wire/commands/crud.js.map +0 -1
- package/dist/wire/commands/hello.d.ts +0 -35
- package/dist/wire/commands/hello.d.ts.map +0 -1
- package/dist/wire/commands/hello.js +0 -204
- package/dist/wire/commands/hello.js.map +0 -1
- package/dist/wire/commands/index.d.ts +0 -24
- package/dist/wire/commands/index.d.ts.map +0 -1
- package/dist/wire/commands/index.js +0 -145
- package/dist/wire/commands/index.js.map +0 -1
- package/dist/wire/commands/router.d.ts +0 -46
- package/dist/wire/commands/router.d.ts.map +0 -1
- package/dist/wire/commands/router.js +0 -151
- package/dist/wire/commands/router.js.map +0 -1
- package/dist/wire/commands/types.d.ts +0 -51
- package/dist/wire/commands/types.d.ts.map +0 -1
- package/dist/wire/commands/types.js +0 -15
- package/dist/wire/commands/types.js.map +0 -1
- package/dist/wire/index.d.ts +0 -15
- package/dist/wire/index.d.ts.map +0 -1
- package/dist/wire/index.js +0 -19
- package/dist/wire/index.js.map +0 -1
- package/dist/wire/message.d.ts +0 -49
- package/dist/wire/message.d.ts.map +0 -1
- package/dist/wire/message.js +0 -299
- package/dist/wire/message.js.map +0 -1
- package/dist/wire/server.d.ts +0 -145
- package/dist/wire/server.d.ts.map +0 -1
- package/dist/wire/server.js +0 -284
- package/dist/wire/server.js.map +0 -1
- package/dist/wire/types.d.ts +0 -140
- package/dist/wire/types.d.ts.map +0 -1
- package/dist/wire/types.js +0 -64
- package/dist/wire/types.js.map +0 -1
- package/dist/worker.d.ts.map +0 -1
|
@@ -1,1276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QueryTranslator - Translates MongoDB-style queries to SQL
|
|
3
|
-
* using json_extract for field access on JSON documents.
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - Operator registry pattern for extensibility
|
|
7
|
-
* - Automatic flattening of nested $and/$or for SQL optimization
|
|
8
|
-
* - CTE-based optimization for multiple array operations
|
|
9
|
-
* - Parameterized queries for SQL injection prevention
|
|
10
|
-
* - $text operator for full-text search with FTS5
|
|
11
|
-
* - Multi-dialect support (SQLite, ClickHouse)
|
|
12
|
-
*/
|
|
13
|
-
import { validateFieldPath } from '../utils/sql-safety.js';
|
|
14
|
-
import { validateDialect, jsonExtract as dialectJsonExtract, jsonType as dialectJsonType, jsonTypeWithPath as dialectJsonTypeWithPath, jsonArrayLength as dialectJsonArrayLength, regexMatch as dialectRegexMatch, } from './dialect.js';
|
|
15
|
-
import { translateExpression } from './stages/expression-translator.js';
|
|
16
|
-
/**
|
|
17
|
-
* MongoDB type to SQLite json_type mapping
|
|
18
|
-
*/
|
|
19
|
-
const MONGO_TYPE_TO_SQLITE = {
|
|
20
|
-
string: 'text',
|
|
21
|
-
number: ['integer', 'real'],
|
|
22
|
-
bool: ['true', 'false'],
|
|
23
|
-
boolean: ['true', 'false'],
|
|
24
|
-
array: 'array',
|
|
25
|
-
object: 'object',
|
|
26
|
-
null: 'null',
|
|
27
|
-
};
|
|
28
|
-
const DEFAULT_OPTIONS = {
|
|
29
|
-
useCTE: true,
|
|
30
|
-
flattenLogical: true,
|
|
31
|
-
dialect: 'sqlite',
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* QueryTranslator - Converts MongoDB query syntax to SQL with json_extract
|
|
35
|
-
*/
|
|
36
|
-
export class QueryTranslator {
|
|
37
|
-
options;
|
|
38
|
-
dialect;
|
|
39
|
-
constructor(options = {}) {
|
|
40
|
-
// Validate dialect before merging options
|
|
41
|
-
const dialect = validateDialect(options.dialect);
|
|
42
|
-
this.options = { ...DEFAULT_OPTIONS, ...options, dialect };
|
|
43
|
-
this.dialect = dialect;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Registry of comparison operators and their SQL translations
|
|
47
|
-
*/
|
|
48
|
-
comparisonOperators = {
|
|
49
|
-
$eq: (path, value, params) => {
|
|
50
|
-
if (value === null) {
|
|
51
|
-
return `${this.jsonExtract(path)} IS NULL`;
|
|
52
|
-
}
|
|
53
|
-
// SQLite's json_extract returns 1/0 for booleans, so convert JS booleans
|
|
54
|
-
const sqlValue = typeof value === 'boolean' ? (value ? 1 : 0) : value;
|
|
55
|
-
params.push(sqlValue);
|
|
56
|
-
return `${this.jsonExtract(path)} = ?`;
|
|
57
|
-
},
|
|
58
|
-
$ne: (path, value, params) => {
|
|
59
|
-
if (value === null) {
|
|
60
|
-
return `${this.jsonExtract(path)} IS NOT NULL`;
|
|
61
|
-
}
|
|
62
|
-
// SQLite's json_extract returns 1/0 for booleans, so convert JS booleans
|
|
63
|
-
const sqlValue = typeof value === 'boolean' ? (value ? 1 : 0) : value;
|
|
64
|
-
params.push(sqlValue);
|
|
65
|
-
return `${this.jsonExtract(path)} != ?`;
|
|
66
|
-
},
|
|
67
|
-
$gt: (path, value, params) => {
|
|
68
|
-
params.push(value);
|
|
69
|
-
return `${this.jsonExtract(path)} > ?`;
|
|
70
|
-
},
|
|
71
|
-
$gte: (path, value, params) => {
|
|
72
|
-
params.push(value);
|
|
73
|
-
return `${this.jsonExtract(path)} >= ?`;
|
|
74
|
-
},
|
|
75
|
-
$lt: (path, value, params) => {
|
|
76
|
-
params.push(value);
|
|
77
|
-
return `${this.jsonExtract(path)} < ?`;
|
|
78
|
-
},
|
|
79
|
-
$lte: (path, value, params) => {
|
|
80
|
-
params.push(value);
|
|
81
|
-
return `${this.jsonExtract(path)} <= ?`;
|
|
82
|
-
},
|
|
83
|
-
$in: (path, value, params) => {
|
|
84
|
-
const arr = value;
|
|
85
|
-
if (arr.length === 0) {
|
|
86
|
-
return '0 = 1';
|
|
87
|
-
}
|
|
88
|
-
params.push(...arr);
|
|
89
|
-
const placeholders = arr.map(() => '?').join(', ');
|
|
90
|
-
return `${this.jsonExtract(path)} IN (${placeholders})`;
|
|
91
|
-
},
|
|
92
|
-
$nin: (path, value, params) => {
|
|
93
|
-
const arr = value;
|
|
94
|
-
if (arr.length === 0) {
|
|
95
|
-
return '1 = 1';
|
|
96
|
-
}
|
|
97
|
-
params.push(...arr);
|
|
98
|
-
const placeholders = arr.map(() => '?').join(', ');
|
|
99
|
-
return `${this.jsonExtract(path)} NOT IN (${placeholders})`;
|
|
100
|
-
},
|
|
101
|
-
$regex: (path, value, params) => {
|
|
102
|
-
// Handle both { $regex: "pattern" } and { $regex: "pattern", $options: "i" }
|
|
103
|
-
// Also handle direct { field: { $regex: /pattern/i } } form
|
|
104
|
-
// Also support $regexType: 'glob' for SQLite GLOB syntax
|
|
105
|
-
let pattern;
|
|
106
|
-
let options = '';
|
|
107
|
-
let regexType = 'like'; // default to LIKE pattern matching
|
|
108
|
-
if (typeof value === 'string') {
|
|
109
|
-
pattern = value;
|
|
110
|
-
}
|
|
111
|
-
else if (value instanceof RegExp) {
|
|
112
|
-
pattern = value.source;
|
|
113
|
-
options = value.flags;
|
|
114
|
-
}
|
|
115
|
-
else if (value && typeof value === 'object') {
|
|
116
|
-
const regexObj = value;
|
|
117
|
-
pattern = regexObj.$regex || '';
|
|
118
|
-
options = regexObj.$options || '';
|
|
119
|
-
regexType = regexObj.$regexType || 'like';
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
pattern = String(value);
|
|
123
|
-
}
|
|
124
|
-
const fieldExpr = this.jsonExtract(path);
|
|
125
|
-
const caseInsensitive = options.includes('i');
|
|
126
|
-
// Handle GLOB type for SQLite
|
|
127
|
-
if (regexType === 'glob' && this.dialect === 'sqlite') {
|
|
128
|
-
params.push(pattern);
|
|
129
|
-
const typeCheck = `json_type(${fieldExpr}) = 'text'`;
|
|
130
|
-
return `(${typeCheck} AND ${fieldExpr} GLOB ?)`;
|
|
131
|
-
}
|
|
132
|
-
// Convert regex pattern to LIKE pattern
|
|
133
|
-
// This is a simplified conversion that handles common cases
|
|
134
|
-
const likePattern = this.regexToLike(pattern, options);
|
|
135
|
-
params.push(likePattern);
|
|
136
|
-
// First ensure the field is a string type (regex only works on strings)
|
|
137
|
-
if (this.dialect === 'clickhouse') {
|
|
138
|
-
const typeCheck = `JSONType(data, ${path.replace(/^\$\.?/, '').split('.').map(p => `'${p}'`).join(', ')}) = 'String'`;
|
|
139
|
-
const matchExpr = dialectRegexMatch(this.dialect, fieldExpr, '?', caseInsensitive);
|
|
140
|
-
return `(${typeCheck} AND ${matchExpr})`;
|
|
141
|
-
}
|
|
142
|
-
const typeCheck = `json_type(${fieldExpr}) = 'text'`;
|
|
143
|
-
const matchExpr = dialectRegexMatch(this.dialect, fieldExpr, '?', caseInsensitive);
|
|
144
|
-
return `(${typeCheck} AND ${matchExpr})`;
|
|
145
|
-
},
|
|
146
|
-
$mod: (path, value, params) => {
|
|
147
|
-
// $mod: [divisor, remainder] - matches if field % divisor == remainder
|
|
148
|
-
const [divisor, remainder] = value;
|
|
149
|
-
params.push(divisor, remainder);
|
|
150
|
-
// Check that the field is numeric and apply modulo
|
|
151
|
-
// Use CAST to handle float truncation like MongoDB
|
|
152
|
-
return `(json_type(${this.jsonExtract(path)}) IN ('integer', 'real') AND CAST(${this.jsonExtract(path)} AS INTEGER) % ? = ?)`;
|
|
153
|
-
},
|
|
154
|
-
// Bitwise operators
|
|
155
|
-
$bitsAllSet: (path, value, params) => {
|
|
156
|
-
// $bitsAllSet: [bit positions] or bitmask number
|
|
157
|
-
// Matches if all specified bits are set (1)
|
|
158
|
-
const mask = this.resolveBitmask(value);
|
|
159
|
-
params.push(mask, mask);
|
|
160
|
-
// (field & mask) == mask means all bits in mask are set
|
|
161
|
-
return `(json_type(${this.jsonExtract(path)}) IN ('integer', 'real') AND (CAST(${this.jsonExtract(path)} AS INTEGER) & ?) = ?)`;
|
|
162
|
-
},
|
|
163
|
-
$bitsAnyClear: (path, value, params) => {
|
|
164
|
-
// $bitsAnyClear: [bit positions] or bitmask number
|
|
165
|
-
// Matches if any of the specified bits are clear (0)
|
|
166
|
-
const mask = this.resolveBitmask(value);
|
|
167
|
-
params.push(mask, mask);
|
|
168
|
-
// (field & mask) != mask means at least one bit in mask is clear
|
|
169
|
-
return `(json_type(${this.jsonExtract(path)}) IN ('integer', 'real') AND (CAST(${this.jsonExtract(path)} AS INTEGER) & ?) != ?)`;
|
|
170
|
-
},
|
|
171
|
-
$bitsAllClear: (path, value, params) => {
|
|
172
|
-
// $bitsAllClear: [bit positions] or bitmask number
|
|
173
|
-
// Matches if all specified bits are clear (0)
|
|
174
|
-
const mask = this.resolveBitmask(value);
|
|
175
|
-
params.push(mask);
|
|
176
|
-
// (field & mask) == 0 means all bits in mask are clear
|
|
177
|
-
return `(json_type(${this.jsonExtract(path)}) IN ('integer', 'real') AND (CAST(${this.jsonExtract(path)} AS INTEGER) & ?) = 0)`;
|
|
178
|
-
},
|
|
179
|
-
$bitsAnySet: (path, value, params) => {
|
|
180
|
-
// $bitsAnySet: [bit positions] or bitmask number
|
|
181
|
-
// Matches if any of the specified bits are set (1)
|
|
182
|
-
const mask = this.resolveBitmask(value);
|
|
183
|
-
params.push(mask);
|
|
184
|
-
// (field & mask) != 0 means at least one bit in mask is set
|
|
185
|
-
return `(json_type(${this.jsonExtract(path)}) IN ('integer', 'real') AND (CAST(${this.jsonExtract(path)} AS INTEGER) & ?) != 0)`;
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
/**
|
|
189
|
-
* Registry of element operators
|
|
190
|
-
*/
|
|
191
|
-
elementOperators = {
|
|
192
|
-
$exists: (path, value, _params) => {
|
|
193
|
-
// MongoDB $exists distinguishes between missing fields and null values:
|
|
194
|
-
// - $exists: true -> field exists (including explicit null values)
|
|
195
|
-
// - $exists: false -> field is completely missing from the document
|
|
196
|
-
//
|
|
197
|
-
// SQLite's json_extract returns NULL for both missing fields AND null values,
|
|
198
|
-
// but json_type returns 'null' for explicit nulls and NULL for missing fields.
|
|
199
|
-
// So we use json_type with path directly to properly detect field existence.
|
|
200
|
-
if (path.startsWith('$')) {
|
|
201
|
-
// JSON path - use json_type(data, path) directly for existence checks
|
|
202
|
-
const typeExpr = dialectJsonTypeWithPath(this.dialect, 'data', path);
|
|
203
|
-
if (value) {
|
|
204
|
-
return `${typeExpr} IS NOT NULL`;
|
|
205
|
-
}
|
|
206
|
-
return `${typeExpr} IS NULL`;
|
|
207
|
-
}
|
|
208
|
-
// Direct reference (for elemMatch context) - use json_type
|
|
209
|
-
if (this.dialect === 'clickhouse') {
|
|
210
|
-
if (value) {
|
|
211
|
-
return `JSONType(${path}) IS NOT NULL`;
|
|
212
|
-
}
|
|
213
|
-
return `JSONType(${path}) IS NULL`;
|
|
214
|
-
}
|
|
215
|
-
if (value) {
|
|
216
|
-
return `json_type(${path}) IS NOT NULL`;
|
|
217
|
-
}
|
|
218
|
-
return `json_type(${path}) IS NULL`;
|
|
219
|
-
},
|
|
220
|
-
$type: (path, value, _params) => {
|
|
221
|
-
const mongoType = value;
|
|
222
|
-
const sqliteType = MONGO_TYPE_TO_SQLITE[mongoType];
|
|
223
|
-
const typeExpr = dialectJsonType(this.dialect, 'data', path);
|
|
224
|
-
if (Array.isArray(sqliteType)) {
|
|
225
|
-
if (mongoType === 'number') {
|
|
226
|
-
if (this.dialect === 'clickhouse') {
|
|
227
|
-
return `${typeExpr} IN ('Int64', 'Float64', 'UInt64')`;
|
|
228
|
-
}
|
|
229
|
-
return `${typeExpr} IN ('integer', 'real')`;
|
|
230
|
-
}
|
|
231
|
-
// bool type checks for true/false values
|
|
232
|
-
if (this.dialect === 'clickhouse') {
|
|
233
|
-
return `${typeExpr} = 'Bool'`;
|
|
234
|
-
}
|
|
235
|
-
return `${typeExpr} IN ('true', 'false')`;
|
|
236
|
-
}
|
|
237
|
-
if (this.dialect === 'clickhouse') {
|
|
238
|
-
const chType = mongoType === 'string' ? 'String' : mongoType === 'array' ? 'Array' : mongoType === 'object' ? 'Object' : sqliteType;
|
|
239
|
-
return `${typeExpr} = '${chType}'`;
|
|
240
|
-
}
|
|
241
|
-
return `${typeExpr} = '${sqliteType}'`;
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
/**
|
|
245
|
-
* Registry of array operators
|
|
246
|
-
*/
|
|
247
|
-
arrayOperators = {
|
|
248
|
-
$size: (path, value, params) => {
|
|
249
|
-
params.push(value);
|
|
250
|
-
const lenExpr = dialectJsonArrayLength(this.dialect, 'data', path);
|
|
251
|
-
return `${lenExpr} = ?`;
|
|
252
|
-
},
|
|
253
|
-
$all: (path, value, params) => {
|
|
254
|
-
const arr = value;
|
|
255
|
-
if (arr.length === 0) {
|
|
256
|
-
return '1 = 1';
|
|
257
|
-
}
|
|
258
|
-
// Each value must exist in the array using EXISTS with json_each
|
|
259
|
-
const conditions = arr.map((v) => {
|
|
260
|
-
params.push(v);
|
|
261
|
-
return `EXISTS (SELECT 1 FROM json_each(${this.jsonExtract(path)}) WHERE value = ?)`;
|
|
262
|
-
});
|
|
263
|
-
return conditions.length === 1
|
|
264
|
-
? conditions[0]
|
|
265
|
-
: `(${conditions.join(' AND ')})`;
|
|
266
|
-
},
|
|
267
|
-
$elemMatch: (path, value, params) => {
|
|
268
|
-
const conditions = value;
|
|
269
|
-
// Generate subquery for array element matching
|
|
270
|
-
const innerConditions = this.translateElemMatchConditions(conditions, params);
|
|
271
|
-
return `EXISTS (SELECT 1 FROM json_each(${this.jsonExtract(path)}) WHERE ${innerConditions})`;
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
/**
|
|
275
|
-
* Main entry point - translate a MongoDB query to SQL
|
|
276
|
-
*/
|
|
277
|
-
translate(query) {
|
|
278
|
-
const params = [];
|
|
279
|
-
if (Object.keys(query).length === 0) {
|
|
280
|
-
return { sql: '1 = 1', params: [] };
|
|
281
|
-
}
|
|
282
|
-
// Pre-process to flatten nested logical operators if enabled
|
|
283
|
-
const processedQuery = this.options.flattenLogical
|
|
284
|
-
? this.flattenLogicalOperators(query)
|
|
285
|
-
: query;
|
|
286
|
-
const sql = this.translateDocument(processedQuery, params);
|
|
287
|
-
return { sql, params };
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Flatten nested logical operators of the same type
|
|
291
|
-
* E.g., $and: [{ $and: [a, b] }, c] -> $and: [a, b, c]
|
|
292
|
-
*/
|
|
293
|
-
flattenLogicalOperators(query) {
|
|
294
|
-
const result = {};
|
|
295
|
-
for (const [key, value] of Object.entries(query)) {
|
|
296
|
-
if (key === '$and' || key === '$or') {
|
|
297
|
-
const conditions = value;
|
|
298
|
-
const flattened = [];
|
|
299
|
-
for (const condition of conditions) {
|
|
300
|
-
// Recursively flatten nested conditions
|
|
301
|
-
const flatCondition = this.flattenLogicalOperators(condition);
|
|
302
|
-
// If the nested condition is the same logical operator, merge it
|
|
303
|
-
if (Object.keys(flatCondition).length === 1 && flatCondition[key]) {
|
|
304
|
-
const nestedConditions = flatCondition[key];
|
|
305
|
-
flattened.push(...nestedConditions);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
flattened.push(flatCondition);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
result[key] = flattened;
|
|
312
|
-
}
|
|
313
|
-
else if (key === '$nor') {
|
|
314
|
-
// $nor cannot be flattened the same way, but we still process nested conditions
|
|
315
|
-
const conditions = value;
|
|
316
|
-
result[key] = conditions.map(c => this.flattenLogicalOperators(c));
|
|
317
|
-
}
|
|
318
|
-
else if (key.startsWith('$')) {
|
|
319
|
-
// Other operators, just copy
|
|
320
|
-
result[key] = value;
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
// Field condition - recursively process if it's an object
|
|
324
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
325
|
-
const operators = value;
|
|
326
|
-
const processedOps = {};
|
|
327
|
-
for (const [op, opValue] of Object.entries(operators)) {
|
|
328
|
-
if (op === '$not' && opValue && typeof opValue === 'object') {
|
|
329
|
-
processedOps[op] = this.flattenLogicalOperators(opValue);
|
|
330
|
-
}
|
|
331
|
-
else if (op === '$elemMatch' && opValue && typeof opValue === 'object') {
|
|
332
|
-
processedOps[op] = this.flattenLogicalOperators(opValue);
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
processedOps[op] = opValue;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
result[key] = processedOps;
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
result[key] = value;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return result;
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Translate a query document (top-level or nested)
|
|
349
|
-
*/
|
|
350
|
-
translateDocument(query, params) {
|
|
351
|
-
const conditions = [];
|
|
352
|
-
for (const [key, value] of Object.entries(query)) {
|
|
353
|
-
if (key.startsWith('$')) {
|
|
354
|
-
// Logical operator at top level
|
|
355
|
-
const sql = this.translateLogicalOperator(key, value, params);
|
|
356
|
-
conditions.push(sql);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
// Field condition
|
|
360
|
-
const sql = this.translateField(key, value, params);
|
|
361
|
-
conditions.push(sql);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (conditions.length === 0) {
|
|
365
|
-
return '1 = 1';
|
|
366
|
-
}
|
|
367
|
-
if (conditions.length === 1) {
|
|
368
|
-
return conditions[0];
|
|
369
|
-
}
|
|
370
|
-
return `(${conditions.join(' AND ')})`;
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Translate a field condition
|
|
374
|
-
*/
|
|
375
|
-
translateField(field, value, params) {
|
|
376
|
-
const path = this.fieldToJsonPath(field);
|
|
377
|
-
// Direct value comparison (implicit $eq)
|
|
378
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
379
|
-
return this.comparisonOperators.$eq(path, value, params);
|
|
380
|
-
}
|
|
381
|
-
// Object with operators
|
|
382
|
-
const operators = value;
|
|
383
|
-
const operatorKeys = Object.keys(operators);
|
|
384
|
-
// Check if it's an object with operators
|
|
385
|
-
if (operatorKeys.length > 0 && operatorKeys.every(k => k.startsWith('$'))) {
|
|
386
|
-
return this.translateFieldConditions(path, operators, params, false);
|
|
387
|
-
}
|
|
388
|
-
// Plain object equality (implicit $eq)
|
|
389
|
-
return this.comparisonOperators.$eq(path, value, params);
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Translate conditions on a single field
|
|
393
|
-
*/
|
|
394
|
-
translateFieldConditions(path, conditions, params, isElemMatch) {
|
|
395
|
-
const sqlParts = [];
|
|
396
|
-
// Check for $regex with sibling $options or $regexType
|
|
397
|
-
const hasRegexWithOptions = '$regex' in conditions && ('$options' in conditions || '$regexType' in conditions);
|
|
398
|
-
for (const [op, value] of Object.entries(conditions)) {
|
|
399
|
-
// Skip $options and $regexType when they're siblings of $regex (handled together with $regex)
|
|
400
|
-
if ((op === '$options' || op === '$regexType') && hasRegexWithOptions) {
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
let sql;
|
|
404
|
-
if (op === '$not') {
|
|
405
|
-
// $not wraps another operator
|
|
406
|
-
const innerConditions = value;
|
|
407
|
-
const innerSql = this.translateFieldConditions(path, innerConditions, params, isElemMatch);
|
|
408
|
-
sql = `NOT (${innerSql})`;
|
|
409
|
-
}
|
|
410
|
-
else if (op === '$regex' && hasRegexWithOptions) {
|
|
411
|
-
// Handle $regex with sibling $options or $regexType
|
|
412
|
-
const actualPath = isElemMatch ? this.elemMatchFieldPath(path, '') : path;
|
|
413
|
-
const regexValue = {
|
|
414
|
-
$regex: value,
|
|
415
|
-
$options: conditions.$options,
|
|
416
|
-
$regexType: conditions.$regexType
|
|
417
|
-
};
|
|
418
|
-
sql = this.comparisonOperators[op](actualPath, regexValue, params);
|
|
419
|
-
}
|
|
420
|
-
else if (this.comparisonOperators[op]) {
|
|
421
|
-
const actualPath = isElemMatch ? this.elemMatchFieldPath(path, '') : path;
|
|
422
|
-
sql = this.comparisonOperators[op](actualPath, value, params);
|
|
423
|
-
}
|
|
424
|
-
else if (this.elementOperators[op]) {
|
|
425
|
-
const actualPath = isElemMatch ? this.elemMatchFieldPath(path, '') : path;
|
|
426
|
-
sql = this.elementOperators[op](actualPath, value, params);
|
|
427
|
-
}
|
|
428
|
-
else if (this.arrayOperators[op]) {
|
|
429
|
-
const actualPath = isElemMatch ? this.elemMatchFieldPath(path, '') : path;
|
|
430
|
-
sql = this.arrayOperators[op](actualPath, value, params);
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
// Unknown operator - treat as nested field in elemMatch context
|
|
434
|
-
if (isElemMatch) {
|
|
435
|
-
const nestedPath = this.elemMatchFieldPath(path, op.replace('$', ''));
|
|
436
|
-
sql = this.translateFieldConditions(nestedPath, { $eq: value }, params, true);
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
throw new Error(`Unknown operator: ${op}`);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
sqlParts.push(sql);
|
|
443
|
-
}
|
|
444
|
-
if (sqlParts.length === 0) {
|
|
445
|
-
return '1 = 1';
|
|
446
|
-
}
|
|
447
|
-
if (sqlParts.length === 1) {
|
|
448
|
-
return sqlParts[0];
|
|
449
|
-
}
|
|
450
|
-
return `(${sqlParts.join(' AND ')})`;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Translate logical operators ($and, $or, $not, $nor, $text)
|
|
454
|
-
*/
|
|
455
|
-
translateLogicalOperator(op, value, params) {
|
|
456
|
-
switch (op) {
|
|
457
|
-
case '$and': {
|
|
458
|
-
const conditions = value;
|
|
459
|
-
if (conditions.length === 0) {
|
|
460
|
-
return '1 = 1';
|
|
461
|
-
}
|
|
462
|
-
const parts = conditions.map(c => this.translateDocument(c, params));
|
|
463
|
-
if (parts.length === 1) {
|
|
464
|
-
return parts[0];
|
|
465
|
-
}
|
|
466
|
-
return `(${parts.join(' AND ')})`;
|
|
467
|
-
}
|
|
468
|
-
case '$or': {
|
|
469
|
-
const conditions = value;
|
|
470
|
-
if (conditions.length === 0) {
|
|
471
|
-
return '0 = 1';
|
|
472
|
-
}
|
|
473
|
-
const parts = conditions.map(c => this.translateDocument(c, params));
|
|
474
|
-
if (parts.length === 1) {
|
|
475
|
-
return parts[0];
|
|
476
|
-
}
|
|
477
|
-
return `(${parts.join(' OR ')})`;
|
|
478
|
-
}
|
|
479
|
-
case '$nor': {
|
|
480
|
-
const conditions = value;
|
|
481
|
-
if (conditions.length === 0) {
|
|
482
|
-
return '1 = 1';
|
|
483
|
-
}
|
|
484
|
-
const parts = conditions.map(c => this.translateDocument(c, params));
|
|
485
|
-
return `NOT (${parts.join(' OR ')})`;
|
|
486
|
-
}
|
|
487
|
-
case '$not': {
|
|
488
|
-
// $not at top level wraps a condition
|
|
489
|
-
const innerSql = this.translateDocument(value, params);
|
|
490
|
-
return `NOT (${innerSql})`;
|
|
491
|
-
}
|
|
492
|
-
case '$text': {
|
|
493
|
-
// $text operator for full-text search
|
|
494
|
-
const textOp = value;
|
|
495
|
-
const { sql } = this.translateTextOperator(textOp, params);
|
|
496
|
-
return sql;
|
|
497
|
-
}
|
|
498
|
-
case '$expr': {
|
|
499
|
-
// $expr allows use of aggregation expressions in the query language
|
|
500
|
-
// It enables comparing fields within the same document
|
|
501
|
-
const exprResult = translateExpression(value, params, this.dialect);
|
|
502
|
-
return exprResult;
|
|
503
|
-
}
|
|
504
|
-
case '$jsonSchema': {
|
|
505
|
-
// $jsonSchema validates documents against a JSON Schema
|
|
506
|
-
const schema = value;
|
|
507
|
-
return this.translateJsonSchema(schema, '$', params);
|
|
508
|
-
}
|
|
509
|
-
case '$where': {
|
|
510
|
-
// $where executes JavaScript expressions - NOT SUPPORTED due to security risks
|
|
511
|
-
// MongoDB's $where allows arbitrary JavaScript execution which is a security risk
|
|
512
|
-
// In a SQL context, we cannot safely execute JavaScript
|
|
513
|
-
throw new Error('$where operator is not supported due to security risks. ' +
|
|
514
|
-
'Use $expr with aggregation expressions instead for field comparisons.');
|
|
515
|
-
}
|
|
516
|
-
default:
|
|
517
|
-
throw new Error(`Unknown logical operator: ${op}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* Convert a field name to a JSON path
|
|
522
|
-
* e.g., "a.b.c" -> "$.a.b.c"
|
|
523
|
-
* e.g., "items.0.name" -> "$.items[0].name"
|
|
524
|
-
*
|
|
525
|
-
* SECURITY: Validates field name to prevent SQL injection attacks.
|
|
526
|
-
* @throws Error if field contains invalid characters
|
|
527
|
-
*/
|
|
528
|
-
fieldToJsonPath(field) {
|
|
529
|
-
// Validate the entire field path to prevent SQL injection
|
|
530
|
-
validateFieldPath(field);
|
|
531
|
-
const parts = field.split('.');
|
|
532
|
-
let path = '$';
|
|
533
|
-
for (const part of parts) {
|
|
534
|
-
// Check if part is a numeric index
|
|
535
|
-
if (/^\d+$/.test(part)) {
|
|
536
|
-
path += `[${part}]`;
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
path += `.${part}`;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return path;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Generate json_extract SQL for a path
|
|
546
|
-
*/
|
|
547
|
-
jsonExtract(path) {
|
|
548
|
-
// If path starts with $, it's a JSON path
|
|
549
|
-
if (path.startsWith('$')) {
|
|
550
|
-
return dialectJsonExtract(this.dialect, 'data', path);
|
|
551
|
-
}
|
|
552
|
-
// Otherwise, it's a direct reference (for elemMatch context)
|
|
553
|
-
return path;
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Convert a bitwise operator value to a bitmask
|
|
557
|
-
* Accepts either:
|
|
558
|
-
* - A number (used directly as bitmask)
|
|
559
|
-
* - An array of bit positions (converted to bitmask)
|
|
560
|
-
*/
|
|
561
|
-
resolveBitmask(value) {
|
|
562
|
-
if (typeof value === 'number') {
|
|
563
|
-
return value;
|
|
564
|
-
}
|
|
565
|
-
if (Array.isArray(value)) {
|
|
566
|
-
// Convert array of bit positions to bitmask
|
|
567
|
-
// e.g., [0, 2, 4] -> 0b10101 = 21
|
|
568
|
-
return value.reduce((mask, pos) => mask | (1 << pos), 0);
|
|
569
|
-
}
|
|
570
|
-
throw new Error('Bitwise operator value must be a number or array of bit positions');
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Generate path for elemMatch field access
|
|
574
|
-
* SECURITY: Validates field name to prevent SQL injection attacks.
|
|
575
|
-
*/
|
|
576
|
-
elemMatchFieldPath(basePath, field) {
|
|
577
|
-
if (basePath === 'value') {
|
|
578
|
-
// Inside json_each, value is the current element
|
|
579
|
-
if (field === '') {
|
|
580
|
-
return 'value';
|
|
581
|
-
}
|
|
582
|
-
// Validate field name to prevent SQL injection
|
|
583
|
-
validateFieldPath(field);
|
|
584
|
-
return `json_extract(value, '$.${field}')`;
|
|
585
|
-
}
|
|
586
|
-
if (field === '') {
|
|
587
|
-
return basePath;
|
|
588
|
-
}
|
|
589
|
-
// Validate field name to prevent SQL injection
|
|
590
|
-
validateFieldPath(field);
|
|
591
|
-
return `${basePath}.${field}`;
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Convert a regex pattern to SQLite LIKE/GLOB pattern
|
|
595
|
-
* This handles common regex patterns:
|
|
596
|
-
* - ^pattern -> pattern% (starts with)
|
|
597
|
-
* - pattern$ -> %pattern (ends with)
|
|
598
|
-
* - ^pattern$ -> pattern (exact match)
|
|
599
|
-
* - .* or .+ -> % (any characters)
|
|
600
|
-
* - . -> _ (single character)
|
|
601
|
-
* - [0-9] -> character class (converted to GLOB syntax)
|
|
602
|
-
* - [a-z] -> character class (converted to GLOB syntax)
|
|
603
|
-
* - Literal text -> %text% (contains, default behavior)
|
|
604
|
-
*
|
|
605
|
-
* @param pattern The regex pattern to convert
|
|
606
|
-
* @param options Regex options (i=case-insensitive, m=multiline)
|
|
607
|
-
*/
|
|
608
|
-
regexToLike(pattern, options = '') {
|
|
609
|
-
// For multiline mode, we need special handling of ^ and $
|
|
610
|
-
// In multiline mode, ^ matches start of line (after \n) and $ matches before \n
|
|
611
|
-
const isMultiline = options.includes('m');
|
|
612
|
-
// Handle anchors
|
|
613
|
-
let startsWithAnchor = pattern.startsWith('^');
|
|
614
|
-
let endsWithAnchor = pattern.endsWith('$') && !pattern.endsWith('\\$');
|
|
615
|
-
// In multiline mode, anchors match line boundaries, not string boundaries
|
|
616
|
-
// Since LIKE can't match line boundaries, we convert to contains match
|
|
617
|
-
if (isMultiline && (startsWithAnchor || endsWithAnchor)) {
|
|
618
|
-
// For multiline, we treat ^ and $ as matching within the string
|
|
619
|
-
// This is an approximation - LIKE can't truly match line boundaries
|
|
620
|
-
// But we can check for patterns after newline or before newline
|
|
621
|
-
startsWithAnchor = false;
|
|
622
|
-
endsWithAnchor = false;
|
|
623
|
-
}
|
|
624
|
-
// Remove anchors for processing
|
|
625
|
-
let processed = pattern;
|
|
626
|
-
if (pattern.startsWith('^')) {
|
|
627
|
-
processed = processed.slice(1);
|
|
628
|
-
}
|
|
629
|
-
if (processed.endsWith('$') && !processed.endsWith('\\$')) {
|
|
630
|
-
processed = processed.slice(0, -1);
|
|
631
|
-
}
|
|
632
|
-
// Process character by character to handle escaping properly
|
|
633
|
-
let result = '';
|
|
634
|
-
let i = 0;
|
|
635
|
-
while (i < processed.length) {
|
|
636
|
-
const char = processed[i];
|
|
637
|
-
const nextChar = processed[i + 1];
|
|
638
|
-
if (char === '\\' && nextChar !== undefined) {
|
|
639
|
-
// Escaped character in regex - keep literal character
|
|
640
|
-
// But we need to escape it for LIKE if it's a special LIKE character
|
|
641
|
-
if (nextChar === '%' || nextChar === '_') {
|
|
642
|
-
result += '\\' + nextChar;
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
result += nextChar;
|
|
646
|
-
}
|
|
647
|
-
i += 2;
|
|
648
|
-
}
|
|
649
|
-
else if (char === '[') {
|
|
650
|
-
// Character class - find the closing bracket
|
|
651
|
-
const endBracket = processed.indexOf(']', i + 1);
|
|
652
|
-
if (endBracket !== -1) {
|
|
653
|
-
const charClass = processed.slice(i + 1, endBracket);
|
|
654
|
-
// Convert common character classes to approximate LIKE patterns
|
|
655
|
-
// [0-9] -> _ (single digit) or % for multiple
|
|
656
|
-
// [a-zA-Z] -> _ (single letter)
|
|
657
|
-
// For now, use _ as a single character match (approximation)
|
|
658
|
-
if (charClass.includes('+') || processed[endBracket + 1] === '+') {
|
|
659
|
-
result += '%';
|
|
660
|
-
i = endBracket + (processed[endBracket + 1] === '+' ? 2 : 1);
|
|
661
|
-
}
|
|
662
|
-
else if (processed[endBracket + 1] === '*') {
|
|
663
|
-
result += '%';
|
|
664
|
-
i = endBracket + 2;
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
result += '_';
|
|
668
|
-
i = endBracket + 1;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
else {
|
|
672
|
-
// No closing bracket, treat [ as literal
|
|
673
|
-
result += char;
|
|
674
|
-
i += 1;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
else if (char === '.' && nextChar === '*') {
|
|
678
|
-
// .* -> % (any characters)
|
|
679
|
-
result += '%';
|
|
680
|
-
i += 2;
|
|
681
|
-
}
|
|
682
|
-
else if (char === '.' && nextChar === '+') {
|
|
683
|
-
// .+ -> % (one or more characters, approximate with %)
|
|
684
|
-
result += '%';
|
|
685
|
-
i += 2;
|
|
686
|
-
}
|
|
687
|
-
else if (char === '.') {
|
|
688
|
-
// . -> _ (single character)
|
|
689
|
-
result += '_';
|
|
690
|
-
i += 1;
|
|
691
|
-
}
|
|
692
|
-
else if (char === '%') {
|
|
693
|
-
// Escape literal % for LIKE
|
|
694
|
-
result += '\\%';
|
|
695
|
-
i += 1;
|
|
696
|
-
}
|
|
697
|
-
else if (char === '_') {
|
|
698
|
-
// Escape literal _ for LIKE
|
|
699
|
-
result += '\\_';
|
|
700
|
-
i += 1;
|
|
701
|
-
}
|
|
702
|
-
else if (char === '+' || char === '*' || char === '?' || char === '|' || char === '(' || char === ')') {
|
|
703
|
-
// Skip regex quantifiers and grouping - not directly translatable to LIKE
|
|
704
|
-
// These would need more sophisticated handling
|
|
705
|
-
i += 1;
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
// Regular character
|
|
709
|
-
result += char;
|
|
710
|
-
i += 1;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
// Apply wildcards based on anchors
|
|
714
|
-
if (!startsWithAnchor && !endsWithAnchor) {
|
|
715
|
-
// No anchors: match anywhere (contains)
|
|
716
|
-
return `%${result}%`;
|
|
717
|
-
}
|
|
718
|
-
else if (startsWithAnchor && !endsWithAnchor) {
|
|
719
|
-
// Starts with anchor only
|
|
720
|
-
return `${result}%`;
|
|
721
|
-
}
|
|
722
|
-
else if (!startsWithAnchor && endsWithAnchor) {
|
|
723
|
-
// Ends with anchor only
|
|
724
|
-
return `%${result}`;
|
|
725
|
-
}
|
|
726
|
-
else {
|
|
727
|
-
// Both anchors: exact match
|
|
728
|
-
return result;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Translate conditions inside $elemMatch
|
|
733
|
-
* This handles document conditions like { field: value, field: { $op: value } }
|
|
734
|
-
* SECURITY: Validates field names to prevent SQL injection attacks.
|
|
735
|
-
*/
|
|
736
|
-
translateElemMatchConditions(conditions, params) {
|
|
737
|
-
const sqlParts = [];
|
|
738
|
-
for (const [field, value] of Object.entries(conditions)) {
|
|
739
|
-
// Validate field name to prevent SQL injection
|
|
740
|
-
validateFieldPath(field);
|
|
741
|
-
// In elemMatch, 'value' refers to the current array element from json_each
|
|
742
|
-
// For nested fields, we use json_extract(value, '$.field')
|
|
743
|
-
const extractPath = `json_extract(value, '$.${field}')`;
|
|
744
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
745
|
-
// Direct value comparison
|
|
746
|
-
if (value === null) {
|
|
747
|
-
sqlParts.push(`${extractPath} IS NULL`);
|
|
748
|
-
}
|
|
749
|
-
else {
|
|
750
|
-
params.push(value);
|
|
751
|
-
sqlParts.push(`${extractPath} = ?`);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
else {
|
|
755
|
-
// Object with operators
|
|
756
|
-
const operators = value;
|
|
757
|
-
const opKeys = Object.keys(operators);
|
|
758
|
-
if (opKeys.length > 0 && opKeys.every(k => k.startsWith('$'))) {
|
|
759
|
-
// It's operators like { $gte: 90 }
|
|
760
|
-
for (const [op, opValue] of Object.entries(operators)) {
|
|
761
|
-
const opSql = this.translateElemMatchOperator(extractPath, op, opValue, params);
|
|
762
|
-
sqlParts.push(opSql);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
else {
|
|
766
|
-
// Plain object equality
|
|
767
|
-
params.push(JSON.stringify(value));
|
|
768
|
-
sqlParts.push(`${extractPath} = json(?)`);
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
if (sqlParts.length === 0) {
|
|
773
|
-
return '1 = 1';
|
|
774
|
-
}
|
|
775
|
-
return sqlParts.length === 1 ? sqlParts[0] : `(${sqlParts.join(' AND ')})`;
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Translate a single operator for elemMatch context
|
|
779
|
-
*/
|
|
780
|
-
translateElemMatchOperator(path, op, value, params) {
|
|
781
|
-
switch (op) {
|
|
782
|
-
case '$eq': {
|
|
783
|
-
if (value === null) {
|
|
784
|
-
return `${path} IS NULL`;
|
|
785
|
-
}
|
|
786
|
-
// SQLite's json_extract returns 1/0 for booleans, so convert JS booleans
|
|
787
|
-
const eqValue = typeof value === 'boolean' ? (value ? 1 : 0) : value;
|
|
788
|
-
params.push(eqValue);
|
|
789
|
-
return `${path} = ?`;
|
|
790
|
-
}
|
|
791
|
-
case '$ne': {
|
|
792
|
-
if (value === null) {
|
|
793
|
-
return `${path} IS NOT NULL`;
|
|
794
|
-
}
|
|
795
|
-
// SQLite's json_extract returns 1/0 for booleans, so convert JS booleans
|
|
796
|
-
const neValue = typeof value === 'boolean' ? (value ? 1 : 0) : value;
|
|
797
|
-
params.push(neValue);
|
|
798
|
-
return `${path} != ?`;
|
|
799
|
-
}
|
|
800
|
-
case '$gt':
|
|
801
|
-
params.push(value);
|
|
802
|
-
return `${path} > ?`;
|
|
803
|
-
case '$gte':
|
|
804
|
-
params.push(value);
|
|
805
|
-
return `${path} >= ?`;
|
|
806
|
-
case '$lt':
|
|
807
|
-
params.push(value);
|
|
808
|
-
return `${path} < ?`;
|
|
809
|
-
case '$lte':
|
|
810
|
-
params.push(value);
|
|
811
|
-
return `${path} <= ?`;
|
|
812
|
-
case '$in': {
|
|
813
|
-
const arr = value;
|
|
814
|
-
if (arr.length === 0)
|
|
815
|
-
return '0 = 1';
|
|
816
|
-
params.push(...arr);
|
|
817
|
-
return `${path} IN (${arr.map(() => '?').join(', ')})`;
|
|
818
|
-
}
|
|
819
|
-
case '$nin': {
|
|
820
|
-
const arr = value;
|
|
821
|
-
if (arr.length === 0)
|
|
822
|
-
return '1 = 1';
|
|
823
|
-
params.push(...arr);
|
|
824
|
-
return `${path} NOT IN (${arr.map(() => '?').join(', ')})`;
|
|
825
|
-
}
|
|
826
|
-
case '$exists':
|
|
827
|
-
// Use json_type to distinguish between null values and missing fields
|
|
828
|
-
return value ? `json_type(${path}) IS NOT NULL` : `json_type(${path}) IS NULL`;
|
|
829
|
-
case '$regex': {
|
|
830
|
-
// Handle $regex in elemMatch context
|
|
831
|
-
let pattern;
|
|
832
|
-
let options = '';
|
|
833
|
-
if (typeof value === 'string') {
|
|
834
|
-
pattern = value;
|
|
835
|
-
}
|
|
836
|
-
else if (value instanceof RegExp) {
|
|
837
|
-
pattern = value.source;
|
|
838
|
-
options = value.flags;
|
|
839
|
-
}
|
|
840
|
-
else if (value && typeof value === 'object') {
|
|
841
|
-
const regexObj = value;
|
|
842
|
-
pattern = regexObj.$regex || '';
|
|
843
|
-
options = regexObj.$options || '';
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
pattern = String(value);
|
|
847
|
-
}
|
|
848
|
-
const likePattern = this.regexToLike(pattern, options);
|
|
849
|
-
params.push(likePattern);
|
|
850
|
-
// Add type check to ensure we only match string values
|
|
851
|
-
const typeCheck = `json_type(${path}) = 'text'`;
|
|
852
|
-
if (options.includes('i')) {
|
|
853
|
-
return `(${typeCheck} AND LOWER(${path}) LIKE LOWER(?))`;
|
|
854
|
-
}
|
|
855
|
-
return `(${typeCheck} AND ${path} LIKE ?)`;
|
|
856
|
-
}
|
|
857
|
-
case '$mod': {
|
|
858
|
-
// Handle $mod in elemMatch context
|
|
859
|
-
const [divisor, remainder] = value;
|
|
860
|
-
params.push(divisor, remainder);
|
|
861
|
-
return `(json_type(${path}) IN ('integer', 'real') AND CAST(${path} AS INTEGER) % ? = ?)`;
|
|
862
|
-
}
|
|
863
|
-
default:
|
|
864
|
-
throw new Error(`Unsupported operator in $elemMatch: ${op}`);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Generate optimized SQL with CTE for multiple array operations on the same field
|
|
869
|
-
* This is useful when you have multiple $all checks or $elemMatch on the same array
|
|
870
|
-
*
|
|
871
|
-
* Example output:
|
|
872
|
-
* WITH array_cte AS (
|
|
873
|
-
* SELECT value FROM json_each(json_extract(data, '$.tags'))
|
|
874
|
-
* )
|
|
875
|
-
* SELECT * FROM documents WHERE
|
|
876
|
-
* EXISTS (SELECT 1 FROM array_cte WHERE value = ?) AND
|
|
877
|
-
* EXISTS (SELECT 1 FROM array_cte WHERE value = ?)
|
|
878
|
-
*/
|
|
879
|
-
translateWithCTE(query, tableName = 'documents') {
|
|
880
|
-
const params = [];
|
|
881
|
-
if (Object.keys(query).length === 0) {
|
|
882
|
-
return { sql: `SELECT * FROM ${tableName}`, params: [] };
|
|
883
|
-
}
|
|
884
|
-
// Collect all array fields that have multiple operations
|
|
885
|
-
const arrayFieldOps = this.collectArrayOperations(query);
|
|
886
|
-
const cteDefinitions = [];
|
|
887
|
-
const cteAliases = new Map();
|
|
888
|
-
let cteIndex = 0;
|
|
889
|
-
// Create CTEs for fields with multiple array operations
|
|
890
|
-
for (const [field, count] of arrayFieldOps.entries()) {
|
|
891
|
-
if (count > 1) {
|
|
892
|
-
const alias = `arr_cte_${cteIndex++}`;
|
|
893
|
-
const path = this.fieldToJsonPath(field);
|
|
894
|
-
cteDefinitions.push(`${alias} AS (SELECT value FROM json_each(json_extract(data, '${path}')))`);
|
|
895
|
-
cteAliases.set(field, alias);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
// Translate the query, replacing repeated json_each with CTE references
|
|
899
|
-
const whereClause = this.translateDocumentWithCTE(query, params, cteAliases);
|
|
900
|
-
let sql;
|
|
901
|
-
if (cteDefinitions.length > 0) {
|
|
902
|
-
sql = `WITH ${cteDefinitions.join(', ')} SELECT * FROM ${tableName} WHERE ${whereClause}`;
|
|
903
|
-
}
|
|
904
|
-
else {
|
|
905
|
-
sql = `SELECT * FROM ${tableName} WHERE ${whereClause}`;
|
|
906
|
-
}
|
|
907
|
-
return { sql, params };
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Collect array operations for CTE optimization analysis
|
|
911
|
-
*/
|
|
912
|
-
collectArrayOperations(query, counts = new Map()) {
|
|
913
|
-
for (const [key, value] of Object.entries(query)) {
|
|
914
|
-
if (key === '$and' || key === '$or' || key === '$nor') {
|
|
915
|
-
const conditions = value;
|
|
916
|
-
for (const condition of conditions) {
|
|
917
|
-
this.collectArrayOperations(condition, counts);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
else if (!key.startsWith('$') && value && typeof value === 'object') {
|
|
921
|
-
const operators = value;
|
|
922
|
-
for (const op of Object.keys(operators)) {
|
|
923
|
-
if (op === '$all' || op === '$elemMatch') {
|
|
924
|
-
counts.set(key, (counts.get(key) || 0) + 1);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
return counts;
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Translate document using CTE aliases where applicable
|
|
933
|
-
*/
|
|
934
|
-
translateDocumentWithCTE(query, params, _cteAliases) {
|
|
935
|
-
// For now, fall back to standard translation
|
|
936
|
-
// CTE optimization would replace json_each references with CTE aliases
|
|
937
|
-
// This is a placeholder for full CTE implementation
|
|
938
|
-
return this.translateDocument(query, params);
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Register a custom comparison operator
|
|
942
|
-
* Allows extending the translator with custom operators
|
|
943
|
-
*/
|
|
944
|
-
registerOperator(name, handler) {
|
|
945
|
-
if (!name.startsWith('$')) {
|
|
946
|
-
throw new Error('Operator name must start with $');
|
|
947
|
-
}
|
|
948
|
-
this.comparisonOperators[name] = handler;
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Register a custom element operator
|
|
952
|
-
*/
|
|
953
|
-
registerElementOperator(name, handler) {
|
|
954
|
-
if (!name.startsWith('$')) {
|
|
955
|
-
throw new Error('Operator name must start with $');
|
|
956
|
-
}
|
|
957
|
-
this.elementOperators[name] = handler;
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Register a custom array operator
|
|
961
|
-
*/
|
|
962
|
-
registerArrayOperator(name, handler) {
|
|
963
|
-
if (!name.startsWith('$')) {
|
|
964
|
-
throw new Error('Operator name must start with $');
|
|
965
|
-
}
|
|
966
|
-
this.arrayOperators[name] = handler;
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Translate a MongoDB $text query to FTS5 MATCH SQL
|
|
970
|
-
*/
|
|
971
|
-
translateTextOperator(textOp, params) {
|
|
972
|
-
const search = textOp.$search;
|
|
973
|
-
const caseSensitive = textOp.$caseSensitive;
|
|
974
|
-
const diacriticSensitive = textOp.$diacriticSensitive;
|
|
975
|
-
// Handle empty search string
|
|
976
|
-
if (!search || search.trim() === '') {
|
|
977
|
-
return { sql: '0 = 1', ftsMatch: '' };
|
|
978
|
-
}
|
|
979
|
-
// Convert MongoDB text search syntax to FTS5 syntax
|
|
980
|
-
const ftsQuery = this.convertToFTS5Query(search, caseSensitive, diacriticSensitive);
|
|
981
|
-
params.push(ftsQuery);
|
|
982
|
-
// Generate the FTS5 MATCH condition
|
|
983
|
-
// This will be joined with the main documents table using rowid
|
|
984
|
-
const sql = `id IN (SELECT rowid FROM {{FTS_TABLE}} WHERE {{FTS_TABLE}} MATCH ?)`;
|
|
985
|
-
return { sql, ftsMatch: ftsQuery };
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* Convert MongoDB text search syntax to FTS5 query syntax
|
|
989
|
-
*
|
|
990
|
-
* MongoDB syntax:
|
|
991
|
-
* - "word" -> matches word
|
|
992
|
-
* - "word1 word2" -> matches word1 OR word2
|
|
993
|
-
* - "\"phrase\"" -> matches exact phrase
|
|
994
|
-
* - "-word" -> excludes word (negation)
|
|
995
|
-
*
|
|
996
|
-
* FTS5 syntax:
|
|
997
|
-
* - "word" -> matches word
|
|
998
|
-
* - "word1 OR word2" -> matches word1 or word2
|
|
999
|
-
* - "word1 word2" -> matches word1 AND word2
|
|
1000
|
-
* - "\"phrase\"" -> matches exact phrase
|
|
1001
|
-
* - "NOT word" -> excludes word
|
|
1002
|
-
*/
|
|
1003
|
-
convertToFTS5Query(search, _caseSensitive, _diacriticSensitive) {
|
|
1004
|
-
// Escape special FTS5 characters except quotes and minus
|
|
1005
|
-
const escaped = search.replace(/[&|()^~*:]/g, (char) => {
|
|
1006
|
-
return '\\' + char;
|
|
1007
|
-
});
|
|
1008
|
-
const tokens = [];
|
|
1009
|
-
let remaining = escaped.trim();
|
|
1010
|
-
// Parse the search string for phrases and terms
|
|
1011
|
-
while (remaining.length > 0) {
|
|
1012
|
-
remaining = remaining.trim();
|
|
1013
|
-
// Check for quoted phrase
|
|
1014
|
-
if (remaining.startsWith('"')) {
|
|
1015
|
-
const endQuote = remaining.indexOf('"', 1);
|
|
1016
|
-
if (endQuote > 1) {
|
|
1017
|
-
const phrase = remaining.slice(1, endQuote);
|
|
1018
|
-
tokens.push(`"${phrase}"`);
|
|
1019
|
-
remaining = remaining.slice(endQuote + 1);
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
// Check for negation
|
|
1024
|
-
if (remaining.startsWith('-')) {
|
|
1025
|
-
const spaceIdx = remaining.indexOf(' ');
|
|
1026
|
-
const term = spaceIdx > 0 ? remaining.slice(1, spaceIdx) : remaining.slice(1);
|
|
1027
|
-
if (term) {
|
|
1028
|
-
tokens.push(`NOT ${term}`);
|
|
1029
|
-
}
|
|
1030
|
-
remaining = spaceIdx > 0 ? remaining.slice(spaceIdx + 1) : '';
|
|
1031
|
-
continue;
|
|
1032
|
-
}
|
|
1033
|
-
// Regular term
|
|
1034
|
-
const spaceIdx = remaining.indexOf(' ');
|
|
1035
|
-
const term = spaceIdx > 0 ? remaining.slice(0, spaceIdx) : remaining;
|
|
1036
|
-
if (term) {
|
|
1037
|
-
tokens.push(term);
|
|
1038
|
-
}
|
|
1039
|
-
remaining = spaceIdx > 0 ? remaining.slice(spaceIdx + 1) : '';
|
|
1040
|
-
}
|
|
1041
|
-
// Join tokens - MongoDB uses OR by default for multiple terms
|
|
1042
|
-
// FTS5 uses AND by default, so we explicitly use OR
|
|
1043
|
-
if (tokens.length === 0) {
|
|
1044
|
-
return '*'; // Match all if no valid tokens
|
|
1045
|
-
}
|
|
1046
|
-
// Separate NOT terms from regular terms
|
|
1047
|
-
const notTerms = tokens.filter(t => t.startsWith('NOT '));
|
|
1048
|
-
const regularTerms = tokens.filter(t => !t.startsWith('NOT '));
|
|
1049
|
-
let query = '';
|
|
1050
|
-
if (regularTerms.length > 0) {
|
|
1051
|
-
// Use OR for regular terms (MongoDB default behavior)
|
|
1052
|
-
query = regularTerms.join(' OR ');
|
|
1053
|
-
}
|
|
1054
|
-
// Add NOT terms with AND
|
|
1055
|
-
if (notTerms.length > 0) {
|
|
1056
|
-
if (query) {
|
|
1057
|
-
query = `(${query}) AND ${notTerms.join(' AND ')}`;
|
|
1058
|
-
}
|
|
1059
|
-
else {
|
|
1060
|
-
// Only negations - need a base to negate from
|
|
1061
|
-
query = `* AND ${notTerms.join(' AND ')}`;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
return query;
|
|
1065
|
-
}
|
|
1066
|
-
/**
|
|
1067
|
-
* Translate a query with $meta projection support for textScore
|
|
1068
|
-
*
|
|
1069
|
-
* @param query The MongoDB query (must contain $text for textScore)
|
|
1070
|
-
* @param projection The projection with potential {$meta: "textScore"} fields
|
|
1071
|
-
* @param sort Optional sort with potential {$meta: "textScore"} fields
|
|
1072
|
-
*/
|
|
1073
|
-
translateWithMeta(query, projection, sort) {
|
|
1074
|
-
const params = [];
|
|
1075
|
-
// Check if query has $text
|
|
1076
|
-
const hasText = '$text' in query;
|
|
1077
|
-
if (!hasText) {
|
|
1078
|
-
// No text search, fall back to regular translation
|
|
1079
|
-
const baseResult = this.translate(query);
|
|
1080
|
-
return baseResult;
|
|
1081
|
-
}
|
|
1082
|
-
// Extract $text operator
|
|
1083
|
-
const textOp = query.$text;
|
|
1084
|
-
const { sql: textSql, ftsMatch } = this.translateTextOperator(textOp, params);
|
|
1085
|
-
// Process remaining query conditions
|
|
1086
|
-
const remainingQuery = {};
|
|
1087
|
-
for (const [key, value] of Object.entries(query)) {
|
|
1088
|
-
if (key !== '$text') {
|
|
1089
|
-
remainingQuery[key] = value;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
let whereClause = textSql;
|
|
1093
|
-
if (Object.keys(remainingQuery).length > 0) {
|
|
1094
|
-
const remainingResult = this.translateDocument(remainingQuery, params);
|
|
1095
|
-
whereClause = `(${textSql}) AND (${remainingResult})`;
|
|
1096
|
-
}
|
|
1097
|
-
// Build SELECT clause with textScore if projected
|
|
1098
|
-
let selectClause = '*';
|
|
1099
|
-
const hasTextScoreProjection = projection && Object.values(projection).some(v => v && typeof v === 'object' && v.$meta === 'textScore');
|
|
1100
|
-
if (hasTextScoreProjection) {
|
|
1101
|
-
// FTS5 uses bm25() for relevance ranking
|
|
1102
|
-
// bm25() returns negative values (more negative = more relevant)
|
|
1103
|
-
// We negate it to get positive scores where higher = more relevant
|
|
1104
|
-
selectClause = '*, -bm25({{FTS_TABLE}}) as rank';
|
|
1105
|
-
}
|
|
1106
|
-
// Build ORDER BY clause
|
|
1107
|
-
let orderByClause = '';
|
|
1108
|
-
if (sort) {
|
|
1109
|
-
const hasTextScoreSort = Object.values(sort).some(v => v && typeof v === 'object' && v.$meta === 'textScore');
|
|
1110
|
-
if (hasTextScoreSort) {
|
|
1111
|
-
// Sort by rank (descending by default for textScore)
|
|
1112
|
-
orderByClause = ' ORDER BY rank DESC';
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
return {
|
|
1116
|
-
sql: `SELECT ${selectClause} WHERE ${whereClause}${orderByClause}`,
|
|
1117
|
-
params,
|
|
1118
|
-
requiresFTS: true,
|
|
1119
|
-
ftsMatch,
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Translate a JSON Schema to SQL conditions
|
|
1124
|
-
* Supports common JSON Schema validation keywords
|
|
1125
|
-
*
|
|
1126
|
-
* @param schema The JSON Schema to validate against
|
|
1127
|
-
* @param path The current JSON path (e.g., "$.field.nested")
|
|
1128
|
-
* @param params The parameters array to push values to
|
|
1129
|
-
*/
|
|
1130
|
-
translateJsonSchema(schema, path, params) {
|
|
1131
|
-
const conditions = [];
|
|
1132
|
-
const jsonPath = path === '$' ? 'data' : `json_extract(data, '${path}')`;
|
|
1133
|
-
const typeExpr = path === '$'
|
|
1134
|
-
? 'json_type(data)'
|
|
1135
|
-
: dialectJsonType(this.dialect, 'data', path);
|
|
1136
|
-
// Handle type/bsonType constraint
|
|
1137
|
-
const schemaType = schema.type || schema.bsonType;
|
|
1138
|
-
if (schemaType) {
|
|
1139
|
-
const types = Array.isArray(schemaType) ? schemaType : [schemaType];
|
|
1140
|
-
const typeConditions = types.map(t => {
|
|
1141
|
-
const sqlType = MONGO_TYPE_TO_SQLITE[t];
|
|
1142
|
-
if (Array.isArray(sqlType)) {
|
|
1143
|
-
return `${typeExpr} IN (${sqlType.map(st => `'${st}'`).join(', ')})`;
|
|
1144
|
-
}
|
|
1145
|
-
return `${typeExpr} = '${sqlType || t}'`;
|
|
1146
|
-
});
|
|
1147
|
-
if (typeConditions.length === 1) {
|
|
1148
|
-
conditions.push(typeConditions[0]);
|
|
1149
|
-
}
|
|
1150
|
-
else {
|
|
1151
|
-
conditions.push(`(${typeConditions.join(' OR ')})`);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
// Handle required fields
|
|
1155
|
-
if (schema.required && schema.required.length > 0) {
|
|
1156
|
-
for (const field of schema.required) {
|
|
1157
|
-
validateFieldPath(field);
|
|
1158
|
-
const fieldPath = path === '$' ? `$.${field}` : `${path}.${field}`;
|
|
1159
|
-
const existsExpr = dialectJsonTypeWithPath(this.dialect, 'data', fieldPath);
|
|
1160
|
-
conditions.push(`${existsExpr} IS NOT NULL`);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
// Handle properties
|
|
1164
|
-
if (schema.properties) {
|
|
1165
|
-
for (const [field, propSchema] of Object.entries(schema.properties)) {
|
|
1166
|
-
validateFieldPath(field);
|
|
1167
|
-
const fieldPath = path === '$' ? `$.${field}` : `${path}.${field}`;
|
|
1168
|
-
const existsExpr = dialectJsonTypeWithPath(this.dialect, 'data', fieldPath);
|
|
1169
|
-
// Property validation only applies if the field exists
|
|
1170
|
-
const propCondition = this.translateJsonSchema(propSchema, fieldPath, params);
|
|
1171
|
-
if (propCondition !== '1 = 1') {
|
|
1172
|
-
conditions.push(`(${existsExpr} IS NULL OR ${propCondition})`);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
// Handle enum constraint
|
|
1177
|
-
if (schema.enum && schema.enum.length > 0) {
|
|
1178
|
-
const enumValues = schema.enum.map(v => {
|
|
1179
|
-
if (v === null)
|
|
1180
|
-
return 'NULL';
|
|
1181
|
-
params.push(typeof v === 'object' ? JSON.stringify(v) : v);
|
|
1182
|
-
return '?';
|
|
1183
|
-
});
|
|
1184
|
-
const nullIncluded = schema.enum.includes(null);
|
|
1185
|
-
const nonNullValues = enumValues.filter(v => v !== 'NULL');
|
|
1186
|
-
if (nullIncluded && nonNullValues.length > 0) {
|
|
1187
|
-
conditions.push(`(${jsonPath} IS NULL OR ${jsonPath} IN (${nonNullValues.join(', ')}))`);
|
|
1188
|
-
}
|
|
1189
|
-
else if (nullIncluded) {
|
|
1190
|
-
conditions.push(`${jsonPath} IS NULL`);
|
|
1191
|
-
}
|
|
1192
|
-
else {
|
|
1193
|
-
conditions.push(`${jsonPath} IN (${enumValues.join(', ')})`);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
// Handle minimum/maximum for numbers
|
|
1197
|
-
if (schema.minimum !== undefined) {
|
|
1198
|
-
params.push(schema.minimum);
|
|
1199
|
-
conditions.push(`${jsonPath} >= ?`);
|
|
1200
|
-
}
|
|
1201
|
-
if (schema.maximum !== undefined) {
|
|
1202
|
-
params.push(schema.maximum);
|
|
1203
|
-
conditions.push(`${jsonPath} <= ?`);
|
|
1204
|
-
}
|
|
1205
|
-
if (schema.exclusiveMinimum !== undefined) {
|
|
1206
|
-
const val = typeof schema.exclusiveMinimum === 'boolean' ? schema.minimum : schema.exclusiveMinimum;
|
|
1207
|
-
if (val !== undefined) {
|
|
1208
|
-
params.push(val);
|
|
1209
|
-
conditions.push(`${jsonPath} > ?`);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
if (schema.exclusiveMaximum !== undefined) {
|
|
1213
|
-
const val = typeof schema.exclusiveMaximum === 'boolean' ? schema.maximum : schema.exclusiveMaximum;
|
|
1214
|
-
if (val !== undefined) {
|
|
1215
|
-
params.push(val);
|
|
1216
|
-
conditions.push(`${jsonPath} < ?`);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
// Handle minLength/maxLength for strings
|
|
1220
|
-
if (schema.minLength !== undefined) {
|
|
1221
|
-
params.push(schema.minLength);
|
|
1222
|
-
conditions.push(`LENGTH(${jsonPath}) >= ?`);
|
|
1223
|
-
}
|
|
1224
|
-
if (schema.maxLength !== undefined) {
|
|
1225
|
-
params.push(schema.maxLength);
|
|
1226
|
-
conditions.push(`LENGTH(${jsonPath}) <= ?`);
|
|
1227
|
-
}
|
|
1228
|
-
// Handle pattern for strings
|
|
1229
|
-
if (schema.pattern) {
|
|
1230
|
-
const likePattern = this.regexToLike(schema.pattern);
|
|
1231
|
-
params.push(likePattern);
|
|
1232
|
-
conditions.push(`${jsonPath} LIKE ?`);
|
|
1233
|
-
}
|
|
1234
|
-
// Handle minItems/maxItems for arrays
|
|
1235
|
-
if (schema.minItems !== undefined) {
|
|
1236
|
-
params.push(schema.minItems);
|
|
1237
|
-
const lenExpr = dialectJsonArrayLength(this.dialect, 'data', path);
|
|
1238
|
-
conditions.push(`${lenExpr} >= ?`);
|
|
1239
|
-
}
|
|
1240
|
-
if (schema.maxItems !== undefined) {
|
|
1241
|
-
params.push(schema.maxItems);
|
|
1242
|
-
const lenExpr = dialectJsonArrayLength(this.dialect, 'data', path);
|
|
1243
|
-
conditions.push(`${lenExpr} <= ?`);
|
|
1244
|
-
}
|
|
1245
|
-
// Handle allOf - all schemas must match
|
|
1246
|
-
if (schema.allOf && schema.allOf.length > 0) {
|
|
1247
|
-
const allOfConditions = schema.allOf.map(s => this.translateJsonSchema(s, path, params));
|
|
1248
|
-
conditions.push(`(${allOfConditions.join(' AND ')})`);
|
|
1249
|
-
}
|
|
1250
|
-
// Handle anyOf - at least one schema must match
|
|
1251
|
-
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
1252
|
-
const anyOfConditions = schema.anyOf.map(s => this.translateJsonSchema(s, path, params));
|
|
1253
|
-
conditions.push(`(${anyOfConditions.join(' OR ')})`);
|
|
1254
|
-
}
|
|
1255
|
-
// Handle oneOf - exactly one schema must match (approximated with OR for SQL)
|
|
1256
|
-
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
1257
|
-
// Note: True oneOf validation (exactly one match) is complex in SQL
|
|
1258
|
-
// We approximate with anyOf behavior since exact oneOf requires counting matches
|
|
1259
|
-
const oneOfConditions = schema.oneOf.map(s => this.translateJsonSchema(s, path, params));
|
|
1260
|
-
conditions.push(`(${oneOfConditions.join(' OR ')})`);
|
|
1261
|
-
}
|
|
1262
|
-
// Handle not - schema must not match
|
|
1263
|
-
if (schema.not) {
|
|
1264
|
-
const notCondition = this.translateJsonSchema(schema.not, path, params);
|
|
1265
|
-
conditions.push(`NOT (${notCondition})`);
|
|
1266
|
-
}
|
|
1267
|
-
if (conditions.length === 0) {
|
|
1268
|
-
return '1 = 1';
|
|
1269
|
-
}
|
|
1270
|
-
if (conditions.length === 1) {
|
|
1271
|
-
return conditions[0];
|
|
1272
|
-
}
|
|
1273
|
-
return `(${conditions.join(' AND ')})`;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
//# sourceMappingURL=query-translator.js.map
|