@query-doctor/core 0.10.8 → 0.10.10
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/schema.cjs +2 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +4 -0
- package/dist/schema.d.cts.map +1 -1
- package/dist/schema.d.mts +4 -0
- package/dist/schema.d.mts.map +1 -1
- package/dist/schema.mjs +2 -0
- package/dist/schema.mjs.map +1 -1
- package/dist/sql/normalized-fingerprint.cjs.map +1 -1
- package/dist/sql/normalized-fingerprint.d.cts.map +1 -1
- package/dist/sql/normalized-fingerprint.d.mts.map +1 -1
- package/dist/sql/normalized-fingerprint.mjs.map +1 -1
- package/dist/sql/nudges.cjs +7 -5
- package/dist/sql/nudges.cjs.map +1 -1
- package/dist/sql/nudges.mjs +7 -5
- package/dist/sql/nudges.mjs.map +1 -1
- package/package.json +1 -1
package/dist/schema.cjs
CHANGED
|
@@ -93,6 +93,7 @@ const FullSchemaFunction = zod.z.object({
|
|
|
93
93
|
definition: zod.z.string()
|
|
94
94
|
});
|
|
95
95
|
const FullSchemaExtension = zod.z.object({
|
|
96
|
+
type: zod.z.literal("extension"),
|
|
96
97
|
extensionName: zod.z.string(),
|
|
97
98
|
version: zod.z.string(),
|
|
98
99
|
schemaName: Identifier
|
|
@@ -132,6 +133,7 @@ const FullSchemaType = zod.z.object({
|
|
|
132
133
|
compositeAttributes: zod.z.array(FullSchemaCompositeAttribute).optional()
|
|
133
134
|
});
|
|
134
135
|
const FullSchemaTrigger = zod.z.object({
|
|
136
|
+
type: zod.z.literal("trigger"),
|
|
135
137
|
schemaName: Identifier,
|
|
136
138
|
tableName: Identifier,
|
|
137
139
|
triggerName: Identifier,
|
package/dist/schema.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.cjs","names":["z","PgIdentifier","SCHEMA_DUMP_SQL"],"sources":["../src/schema.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { SCHEMA_DUMP_SQL } from \"./schema-dump.js\";\nimport type { Postgres } from \"./sql/database.js\";\nimport { PgIdentifier } from \"./sql/pg-identifier.js\";\n\nconst Identifier = z.codec(z.string(), z.custom<PgIdentifier>(), {\n encode: (v) => v.toString(),\n decode: (v) => PgIdentifier.fromString(v),\n});\n\nexport const FullSchemaKeyColumn = z.object({\n type: z.literal(\"indexColumn\"),\n name: Identifier,\n order: z.enum([\"ASC\", \"DESC\"]).optional(),\n nulls: z.enum([\"FIRST\", \"LAST\"]).optional(),\n opclass: z.string().optional(),\n collation: z.string().optional(),\n});\n\nexport type FullSchemaKeyColumn = z.infer<typeof FullSchemaKeyColumn>;\n\nexport const FullSchemaIncludedColumn = z.object({\n name: Identifier,\n});\n\nexport type FullSchemaIncludedColumn = z.infer<typeof FullSchemaIncludedColumn>;\n\nexport const FullSchemaIndex = z.object({\n type: z.literal(\"index\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n indexName: Identifier,\n indexType: z.string(),\n isUnique: z.boolean(),\n isPrimary: z.boolean(),\n isClustered: z.boolean(),\n wherePredicate: z.string().optional(),\n tablespace: z.string().optional(),\n keyColumns: z.array(FullSchemaKeyColumn),\n includedColumns: z.array(FullSchemaIncludedColumn).optional(),\n});\n\nexport type FullSchemaIndex = z.infer<typeof FullSchemaIndex>;\n\nexport const FullSchemaColumn = z.object({\n type: z.literal(\"column\"),\n name: Identifier,\n order: z.number(),\n columnType: z.string(),\n isNullable: z.boolean(),\n defaultValue: z.string().optional(),\n dropped: z.boolean(),\n collation: z.string().optional(),\n storage: z.enum([\"plain\", \"main\", \"external\", \"extended\"]).optional(),\n isIdentity: z.enum([\"always\", \"by default\"]).optional(),\n});\n\nexport type FullSchemaColumn = z.infer<typeof FullSchemaColumn>;\n\nexport const FullSchemaTable = z.object({\n type: z.literal(\"table\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n tablespace: z.string().optional(),\n partitionKeyDef: z.string().optional(),\n columns: z.array(FullSchemaColumn).default([]),\n});\n\nexport type FullSchemaTable = z.infer<typeof FullSchemaTable>;\n\nexport const FullSchemaConstraint = z.object({\n type: z.literal(\"constraint\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n constraintName: Identifier,\n constraintType: z\n .enum([\n \"check\",\n \"foreign_key\",\n \"not_null\",\n \"primary_key\",\n \"unique\",\n \"trigger\",\n \"exclusion\",\n ])\n .or(z.string()),\n definition: z.string(),\n isDeferrable: z.boolean().optional(),\n isInitiallyDeferred: z.boolean().optional(),\n isValidated: z.boolean().optional(),\n backingIndexOid: z.number().optional(),\n});\n\nexport type FullSchemaConstraint = z.infer<typeof FullSchemaConstraint>;\n\nexport const FullSchemaFunction = z.object({\n type: z.literal(\"function\"),\n schemaName: Identifier,\n objectName: Identifier,\n objectType: z.enum([\"function\", \"procedure\", \"aggregate\", \"window function\"]),\n identityArguments: z.string().optional(),\n definition: z.string(),\n});\n\nexport type FullSchemaFunction = z.infer<typeof FullSchemaFunction>;\n\nexport const FullSchemaExtension = z.object({\n extensionName: z.string(),\n version: z.string(),\n schemaName: Identifier,\n});\n\nexport type FullSchemaExtension = z.infer<typeof FullSchemaExtension>;\n\nexport const FullSchemaView = z.object({\n type: z.literal(\"view\"),\n schemaName: Identifier,\n viewName: Identifier,\n objectType: z.enum([\"view\", \"materialized_view\"]),\n definition: z.string(),\n tablespace: z.string().optional(),\n});\n\nexport type FullSchemaView = z.infer<typeof FullSchemaView>;\n\nexport const FullSchemaTypeConstraint = z.object({\n name: Identifier,\n definition: z.string(),\n});\n\nexport type FullSchemaTypeConstraint = z.infer<typeof FullSchemaTypeConstraint>;\n\nexport const FullSchemaCompositeAttribute = z.object({\n type: z.literal(\"compositeAttribute\"),\n name: Identifier,\n attributeType: z.string(),\n collation: Identifier.optional(),\n});\n\nexport type FullSchemaCompositeAttribute = z.infer<\n typeof FullSchemaCompositeAttribute\n>;\n\nexport const FullSchemaType = z.object({\n type: z.literal(\"type\"),\n schemaName: Identifier,\n typeName: Identifier,\n typeCategory: z.enum([\"enum\", \"domain\", \"composite\"]),\n enumLabels: z.array(z.string()).optional(),\n domainBaseType: z.string().optional(),\n domainIsNotNull: z.boolean().optional(),\n domainDefault: z.string().optional(),\n domainConstraints: z.array(FullSchemaTypeConstraint).optional(),\n compositeAttributes: z.array(FullSchemaCompositeAttribute).optional(),\n});\n\nexport type FullSchemaType = z.infer<typeof FullSchemaType>;\n\nexport const FullSchemaTrigger = z.object({\n schemaName: Identifier,\n tableName: Identifier,\n triggerName: Identifier,\n definition: z.string(),\n enabledMode: z.string(),\n});\n\nexport type FullSchemaTrigger = z.infer<typeof FullSchemaTrigger>;\n\nexport const FullSchema = z.object({\n indexes: z.array(FullSchemaIndex).default([]),\n tables: z.array(FullSchemaTable).default([]),\n constraints: z.array(FullSchemaConstraint).default([]),\n functions: z.array(FullSchemaFunction).default([]),\n extensions: z.array(FullSchemaExtension).default([]),\n views: z.array(FullSchemaView).default([]),\n types: z.array(FullSchemaType).default([]),\n triggers: z.array(FullSchemaTrigger).default([]),\n});\n\nexport type FullSchema = z.infer<typeof FullSchema>;\n\nexport async function dumpSchema(db: Postgres): Promise<FullSchema> {\n const rows = await db.exec<{ result: FullSchema }>(SCHEMA_DUMP_SQL);\n return FullSchema.parse(rows[0]?.result ?? {});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.cjs","names":["z","PgIdentifier","SCHEMA_DUMP_SQL"],"sources":["../src/schema.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { SCHEMA_DUMP_SQL } from \"./schema-dump.js\";\nimport type { Postgres } from \"./sql/database.js\";\nimport { PgIdentifier } from \"./sql/pg-identifier.js\";\n\nconst Identifier = z.codec(z.string(), z.custom<PgIdentifier>(), {\n encode: (v) => v.toString(),\n decode: (v) => PgIdentifier.fromString(v),\n});\n\nexport const FullSchemaKeyColumn = z.object({\n type: z.literal(\"indexColumn\"),\n name: Identifier,\n order: z.enum([\"ASC\", \"DESC\"]).optional(),\n nulls: z.enum([\"FIRST\", \"LAST\"]).optional(),\n opclass: z.string().optional(),\n collation: z.string().optional(),\n});\n\nexport type FullSchemaKeyColumn = z.infer<typeof FullSchemaKeyColumn>;\n\nexport const FullSchemaIncludedColumn = z.object({\n name: Identifier,\n});\n\nexport type FullSchemaIncludedColumn = z.infer<typeof FullSchemaIncludedColumn>;\n\nexport const FullSchemaIndex = z.object({\n type: z.literal(\"index\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n indexName: Identifier,\n indexType: z.string(),\n isUnique: z.boolean(),\n isPrimary: z.boolean(),\n isClustered: z.boolean(),\n wherePredicate: z.string().optional(),\n tablespace: z.string().optional(),\n keyColumns: z.array(FullSchemaKeyColumn),\n includedColumns: z.array(FullSchemaIncludedColumn).optional(),\n});\n\nexport type FullSchemaIndex = z.infer<typeof FullSchemaIndex>;\n\nexport const FullSchemaColumn = z.object({\n type: z.literal(\"column\"),\n name: Identifier,\n order: z.number(),\n columnType: z.string(),\n isNullable: z.boolean(),\n defaultValue: z.string().optional(),\n dropped: z.boolean(),\n collation: z.string().optional(),\n storage: z.enum([\"plain\", \"main\", \"external\", \"extended\"]).optional(),\n isIdentity: z.enum([\"always\", \"by default\"]).optional(),\n});\n\nexport type FullSchemaColumn = z.infer<typeof FullSchemaColumn>;\n\nexport const FullSchemaTable = z.object({\n type: z.literal(\"table\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n tablespace: z.string().optional(),\n partitionKeyDef: z.string().optional(),\n columns: z.array(FullSchemaColumn).default([]),\n});\n\nexport type FullSchemaTable = z.infer<typeof FullSchemaTable>;\n\nexport const FullSchemaConstraint = z.object({\n type: z.literal(\"constraint\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n constraintName: Identifier,\n constraintType: z\n .enum([\n \"check\",\n \"foreign_key\",\n \"not_null\",\n \"primary_key\",\n \"unique\",\n \"trigger\",\n \"exclusion\",\n ])\n .or(z.string()),\n definition: z.string(),\n isDeferrable: z.boolean().optional(),\n isInitiallyDeferred: z.boolean().optional(),\n isValidated: z.boolean().optional(),\n backingIndexOid: z.number().optional(),\n});\n\nexport type FullSchemaConstraint = z.infer<typeof FullSchemaConstraint>;\n\nexport const FullSchemaFunction = z.object({\n type: z.literal(\"function\"),\n schemaName: Identifier,\n objectName: Identifier,\n objectType: z.enum([\"function\", \"procedure\", \"aggregate\", \"window function\"]),\n identityArguments: z.string().optional(),\n definition: z.string(),\n});\n\nexport type FullSchemaFunction = z.infer<typeof FullSchemaFunction>;\n\nexport const FullSchemaExtension = z.object({\n type: z.literal(\"extension\"),\n extensionName: z.string(),\n version: z.string(),\n schemaName: Identifier,\n});\n\nexport type FullSchemaExtension = z.infer<typeof FullSchemaExtension>;\n\nexport const FullSchemaView = z.object({\n type: z.literal(\"view\"),\n schemaName: Identifier,\n viewName: Identifier,\n objectType: z.enum([\"view\", \"materialized_view\"]),\n definition: z.string(),\n tablespace: z.string().optional(),\n});\n\nexport type FullSchemaView = z.infer<typeof FullSchemaView>;\n\nexport const FullSchemaTypeConstraint = z.object({\n name: Identifier,\n definition: z.string(),\n});\n\nexport type FullSchemaTypeConstraint = z.infer<typeof FullSchemaTypeConstraint>;\n\nexport const FullSchemaCompositeAttribute = z.object({\n type: z.literal(\"compositeAttribute\"),\n name: Identifier,\n attributeType: z.string(),\n collation: Identifier.optional(),\n});\n\nexport type FullSchemaCompositeAttribute = z.infer<\n typeof FullSchemaCompositeAttribute\n>;\n\nexport const FullSchemaType = z.object({\n type: z.literal(\"type\"),\n schemaName: Identifier,\n typeName: Identifier,\n typeCategory: z.enum([\"enum\", \"domain\", \"composite\"]),\n enumLabels: z.array(z.string()).optional(),\n domainBaseType: z.string().optional(),\n domainIsNotNull: z.boolean().optional(),\n domainDefault: z.string().optional(),\n domainConstraints: z.array(FullSchemaTypeConstraint).optional(),\n compositeAttributes: z.array(FullSchemaCompositeAttribute).optional(),\n});\n\nexport type FullSchemaType = z.infer<typeof FullSchemaType>;\n\nexport const FullSchemaTrigger = z.object({\n type: z.literal(\"trigger\"),\n schemaName: Identifier,\n tableName: Identifier,\n triggerName: Identifier,\n definition: z.string(),\n enabledMode: z.string(),\n});\n\nexport type FullSchemaTrigger = z.infer<typeof FullSchemaTrigger>;\n\nexport const FullSchema = z.object({\n indexes: z.array(FullSchemaIndex).default([]),\n tables: z.array(FullSchemaTable).default([]),\n constraints: z.array(FullSchemaConstraint).default([]),\n functions: z.array(FullSchemaFunction).default([]),\n extensions: z.array(FullSchemaExtension).default([]),\n views: z.array(FullSchemaView).default([]),\n types: z.array(FullSchemaType).default([]),\n triggers: z.array(FullSchemaTrigger).default([]),\n});\n\nexport type FullSchema = z.infer<typeof FullSchema>;\n\nexport async function dumpSchema(db: Postgres): Promise<FullSchema> {\n const rows = await db.exec<{ result: FullSchema }>(SCHEMA_DUMP_SQL);\n return FullSchema.parse(rows[0]?.result ?? {});\n}\n"],"mappings":"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"}
|
package/dist/schema.d.cts
CHANGED
|
@@ -142,6 +142,7 @@ declare const FullSchemaFunction: z.ZodObject<{
|
|
|
142
142
|
}, z.core.$strip>;
|
|
143
143
|
type FullSchemaFunction = z.infer<typeof FullSchemaFunction>;
|
|
144
144
|
declare const FullSchemaExtension: z.ZodObject<{
|
|
145
|
+
type: z.ZodLiteral<"extension">;
|
|
145
146
|
extensionName: z.ZodString;
|
|
146
147
|
version: z.ZodString;
|
|
147
148
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
@@ -197,6 +198,7 @@ declare const FullSchemaType: z.ZodObject<{
|
|
|
197
198
|
}, z.core.$strip>;
|
|
198
199
|
type FullSchemaType = z.infer<typeof FullSchemaType>;
|
|
199
200
|
declare const FullSchemaTrigger: z.ZodObject<{
|
|
201
|
+
type: z.ZodLiteral<"trigger">;
|
|
200
202
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
201
203
|
tableName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
202
204
|
triggerName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
@@ -298,6 +300,7 @@ declare const FullSchema: z.ZodObject<{
|
|
|
298
300
|
definition: z.ZodString;
|
|
299
301
|
}, z.core.$strip>>>;
|
|
300
302
|
extensions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
303
|
+
type: z.ZodLiteral<"extension">;
|
|
301
304
|
extensionName: z.ZodString;
|
|
302
305
|
version: z.ZodString;
|
|
303
306
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
@@ -338,6 +341,7 @@ declare const FullSchema: z.ZodObject<{
|
|
|
338
341
|
}, z.core.$strip>>>;
|
|
339
342
|
}, z.core.$strip>>>;
|
|
340
343
|
triggers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
344
|
+
type: z.ZodLiteral<"trigger">;
|
|
341
345
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
342
346
|
tableName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
343
347
|
triggerName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
package/dist/schema.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.cts","names":[],"sources":["../src/schema.ts"],"mappings":";;;;;;;cAUa,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;KASpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;KAIzB,wBAAA,GAA2B,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,cAEzC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgBhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,gBAAA,EAAgB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;KAajB,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,gBAAA;AAAA,cAEjC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAUhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;KAwBrB,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,cAErC,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;;;;;;;KASnB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,cAEnC,mBAAA,EAAmB,CAAA,CAAA,SAAA
|
|
1
|
+
{"version":3,"file":"schema.d.cts","names":[],"sources":["../src/schema.ts"],"mappings":";;;;;;;cAUa,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;KASpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;KAIzB,wBAAA,GAA2B,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,cAEzC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgBhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,gBAAA,EAAgB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;KAajB,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,gBAAA;AAAA,cAEjC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAUhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;KAwBrB,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,cAErC,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;;;;;;;KASnB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,cAEnC,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;KAOpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,cAAA,EAAc,CAAA,CAAA,SAAA;;;;;;;;;;;KASf,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,cAE/B,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;KAKzB,wBAAA,GAA2B,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,cAEzC,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;;KAO7B,4BAAA,GAA+B,CAAA,CAAE,KAAA,QACpC,4BAAA;AAAA,cAGI,cAAA,EAAc,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;KAaf,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,cAE/B,iBAAA,EAAiB,CAAA,CAAA,SAAA;;;;;;;;KASlB,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,cAElC,UAAA,EAAU,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAWX,UAAA,GAAa,CAAA,CAAE,KAAA,QAAa,UAAA;AAAA,iBAElB,UAAA,CAAW,EAAA,EAAI,QAAA,GAAW,OAAA,CAAQ,UAAA"}
|
package/dist/schema.d.mts
CHANGED
|
@@ -142,6 +142,7 @@ declare const FullSchemaFunction: z.ZodObject<{
|
|
|
142
142
|
}, z.core.$strip>;
|
|
143
143
|
type FullSchemaFunction = z.infer<typeof FullSchemaFunction>;
|
|
144
144
|
declare const FullSchemaExtension: z.ZodObject<{
|
|
145
|
+
type: z.ZodLiteral<"extension">;
|
|
145
146
|
extensionName: z.ZodString;
|
|
146
147
|
version: z.ZodString;
|
|
147
148
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
@@ -197,6 +198,7 @@ declare const FullSchemaType: z.ZodObject<{
|
|
|
197
198
|
}, z.core.$strip>;
|
|
198
199
|
type FullSchemaType = z.infer<typeof FullSchemaType>;
|
|
199
200
|
declare const FullSchemaTrigger: z.ZodObject<{
|
|
201
|
+
type: z.ZodLiteral<"trigger">;
|
|
200
202
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
201
203
|
tableName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
202
204
|
triggerName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
@@ -298,6 +300,7 @@ declare const FullSchema: z.ZodObject<{
|
|
|
298
300
|
definition: z.ZodString;
|
|
299
301
|
}, z.core.$strip>>>;
|
|
300
302
|
extensions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
303
|
+
type: z.ZodLiteral<"extension">;
|
|
301
304
|
extensionName: z.ZodString;
|
|
302
305
|
version: z.ZodString;
|
|
303
306
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
@@ -338,6 +341,7 @@ declare const FullSchema: z.ZodObject<{
|
|
|
338
341
|
}, z.core.$strip>>>;
|
|
339
342
|
}, z.core.$strip>>>;
|
|
340
343
|
triggers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
344
|
+
type: z.ZodLiteral<"trigger">;
|
|
341
345
|
schemaName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
342
346
|
tableName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
|
343
347
|
triggerName: z.ZodCodec<z.ZodString, z.ZodCustom<PgIdentifier, PgIdentifier>>;
|
package/dist/schema.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.mts","names":[],"sources":["../src/schema.ts"],"mappings":";;;;;;;cAUa,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;KASpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;KAIzB,wBAAA,GAA2B,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,cAEzC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgBhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,gBAAA,EAAgB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;KAajB,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,gBAAA;AAAA,cAEjC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAUhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;KAwBrB,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,cAErC,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;;;;;;;KASnB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,cAEnC,mBAAA,EAAmB,CAAA,CAAA,SAAA
|
|
1
|
+
{"version":3,"file":"schema.d.mts","names":[],"sources":["../src/schema.ts"],"mappings":";;;;;;;cAUa,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;KASpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;KAIzB,wBAAA,GAA2B,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,cAEzC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgBhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,gBAAA,EAAgB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;KAajB,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,gBAAA;AAAA,cAEjC,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAUhB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,cAEhC,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;KAwBrB,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,cAErC,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;;;;;;;KASnB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,cAEnC,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;KAOpB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,cAEpC,cAAA,EAAc,CAAA,CAAA,SAAA;;;;;;;;;;;KASf,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,cAE/B,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;KAKzB,wBAAA,GAA2B,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,cAEzC,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;;KAO7B,4BAAA,GAA+B,CAAA,CAAE,KAAA,QACpC,4BAAA;AAAA,cAGI,cAAA,EAAc,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;KAaf,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,cAE/B,iBAAA,EAAiB,CAAA,CAAA,SAAA;;;;;;;;KASlB,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,cAElC,UAAA,EAAU,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAWX,UAAA,GAAa,CAAA,CAAE,KAAA,QAAa,UAAA;AAAA,iBAElB,UAAA,CAAW,EAAA,EAAI,QAAA,GAAW,OAAA,CAAQ,UAAA"}
|
package/dist/schema.mjs
CHANGED
|
@@ -92,6 +92,7 @@ const FullSchemaFunction = z.object({
|
|
|
92
92
|
definition: z.string()
|
|
93
93
|
});
|
|
94
94
|
const FullSchemaExtension = z.object({
|
|
95
|
+
type: z.literal("extension"),
|
|
95
96
|
extensionName: z.string(),
|
|
96
97
|
version: z.string(),
|
|
97
98
|
schemaName: Identifier
|
|
@@ -131,6 +132,7 @@ const FullSchemaType = z.object({
|
|
|
131
132
|
compositeAttributes: z.array(FullSchemaCompositeAttribute).optional()
|
|
132
133
|
});
|
|
133
134
|
const FullSchemaTrigger = z.object({
|
|
135
|
+
type: z.literal("trigger"),
|
|
134
136
|
schemaName: Identifier,
|
|
135
137
|
tableName: Identifier,
|
|
136
138
|
triggerName: Identifier,
|
package/dist/schema.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.mjs","names":[],"sources":["../src/schema.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { SCHEMA_DUMP_SQL } from \"./schema-dump.js\";\nimport type { Postgres } from \"./sql/database.js\";\nimport { PgIdentifier } from \"./sql/pg-identifier.js\";\n\nconst Identifier = z.codec(z.string(), z.custom<PgIdentifier>(), {\n encode: (v) => v.toString(),\n decode: (v) => PgIdentifier.fromString(v),\n});\n\nexport const FullSchemaKeyColumn = z.object({\n type: z.literal(\"indexColumn\"),\n name: Identifier,\n order: z.enum([\"ASC\", \"DESC\"]).optional(),\n nulls: z.enum([\"FIRST\", \"LAST\"]).optional(),\n opclass: z.string().optional(),\n collation: z.string().optional(),\n});\n\nexport type FullSchemaKeyColumn = z.infer<typeof FullSchemaKeyColumn>;\n\nexport const FullSchemaIncludedColumn = z.object({\n name: Identifier,\n});\n\nexport type FullSchemaIncludedColumn = z.infer<typeof FullSchemaIncludedColumn>;\n\nexport const FullSchemaIndex = z.object({\n type: z.literal(\"index\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n indexName: Identifier,\n indexType: z.string(),\n isUnique: z.boolean(),\n isPrimary: z.boolean(),\n isClustered: z.boolean(),\n wherePredicate: z.string().optional(),\n tablespace: z.string().optional(),\n keyColumns: z.array(FullSchemaKeyColumn),\n includedColumns: z.array(FullSchemaIncludedColumn).optional(),\n});\n\nexport type FullSchemaIndex = z.infer<typeof FullSchemaIndex>;\n\nexport const FullSchemaColumn = z.object({\n type: z.literal(\"column\"),\n name: Identifier,\n order: z.number(),\n columnType: z.string(),\n isNullable: z.boolean(),\n defaultValue: z.string().optional(),\n dropped: z.boolean(),\n collation: z.string().optional(),\n storage: z.enum([\"plain\", \"main\", \"external\", \"extended\"]).optional(),\n isIdentity: z.enum([\"always\", \"by default\"]).optional(),\n});\n\nexport type FullSchemaColumn = z.infer<typeof FullSchemaColumn>;\n\nexport const FullSchemaTable = z.object({\n type: z.literal(\"table\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n tablespace: z.string().optional(),\n partitionKeyDef: z.string().optional(),\n columns: z.array(FullSchemaColumn).default([]),\n});\n\nexport type FullSchemaTable = z.infer<typeof FullSchemaTable>;\n\nexport const FullSchemaConstraint = z.object({\n type: z.literal(\"constraint\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n constraintName: Identifier,\n constraintType: z\n .enum([\n \"check\",\n \"foreign_key\",\n \"not_null\",\n \"primary_key\",\n \"unique\",\n \"trigger\",\n \"exclusion\",\n ])\n .or(z.string()),\n definition: z.string(),\n isDeferrable: z.boolean().optional(),\n isInitiallyDeferred: z.boolean().optional(),\n isValidated: z.boolean().optional(),\n backingIndexOid: z.number().optional(),\n});\n\nexport type FullSchemaConstraint = z.infer<typeof FullSchemaConstraint>;\n\nexport const FullSchemaFunction = z.object({\n type: z.literal(\"function\"),\n schemaName: Identifier,\n objectName: Identifier,\n objectType: z.enum([\"function\", \"procedure\", \"aggregate\", \"window function\"]),\n identityArguments: z.string().optional(),\n definition: z.string(),\n});\n\nexport type FullSchemaFunction = z.infer<typeof FullSchemaFunction>;\n\nexport const FullSchemaExtension = z.object({\n extensionName: z.string(),\n version: z.string(),\n schemaName: Identifier,\n});\n\nexport type FullSchemaExtension = z.infer<typeof FullSchemaExtension>;\n\nexport const FullSchemaView = z.object({\n type: z.literal(\"view\"),\n schemaName: Identifier,\n viewName: Identifier,\n objectType: z.enum([\"view\", \"materialized_view\"]),\n definition: z.string(),\n tablespace: z.string().optional(),\n});\n\nexport type FullSchemaView = z.infer<typeof FullSchemaView>;\n\nexport const FullSchemaTypeConstraint = z.object({\n name: Identifier,\n definition: z.string(),\n});\n\nexport type FullSchemaTypeConstraint = z.infer<typeof FullSchemaTypeConstraint>;\n\nexport const FullSchemaCompositeAttribute = z.object({\n type: z.literal(\"compositeAttribute\"),\n name: Identifier,\n attributeType: z.string(),\n collation: Identifier.optional(),\n});\n\nexport type FullSchemaCompositeAttribute = z.infer<\n typeof FullSchemaCompositeAttribute\n>;\n\nexport const FullSchemaType = z.object({\n type: z.literal(\"type\"),\n schemaName: Identifier,\n typeName: Identifier,\n typeCategory: z.enum([\"enum\", \"domain\", \"composite\"]),\n enumLabels: z.array(z.string()).optional(),\n domainBaseType: z.string().optional(),\n domainIsNotNull: z.boolean().optional(),\n domainDefault: z.string().optional(),\n domainConstraints: z.array(FullSchemaTypeConstraint).optional(),\n compositeAttributes: z.array(FullSchemaCompositeAttribute).optional(),\n});\n\nexport type FullSchemaType = z.infer<typeof FullSchemaType>;\n\nexport const FullSchemaTrigger = z.object({\n schemaName: Identifier,\n tableName: Identifier,\n triggerName: Identifier,\n definition: z.string(),\n enabledMode: z.string(),\n});\n\nexport type FullSchemaTrigger = z.infer<typeof FullSchemaTrigger>;\n\nexport const FullSchema = z.object({\n indexes: z.array(FullSchemaIndex).default([]),\n tables: z.array(FullSchemaTable).default([]),\n constraints: z.array(FullSchemaConstraint).default([]),\n functions: z.array(FullSchemaFunction).default([]),\n extensions: z.array(FullSchemaExtension).default([]),\n views: z.array(FullSchemaView).default([]),\n types: z.array(FullSchemaType).default([]),\n triggers: z.array(FullSchemaTrigger).default([]),\n});\n\nexport type FullSchema = z.infer<typeof FullSchema>;\n\nexport async function dumpSchema(db: Postgres): Promise<FullSchema> {\n const rows = await db.exec<{ result: FullSchema }>(SCHEMA_DUMP_SQL);\n return FullSchema.parse(rows[0]?.result ?? {});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.mjs","names":[],"sources":["../src/schema.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { SCHEMA_DUMP_SQL } from \"./schema-dump.js\";\nimport type { Postgres } from \"./sql/database.js\";\nimport { PgIdentifier } from \"./sql/pg-identifier.js\";\n\nconst Identifier = z.codec(z.string(), z.custom<PgIdentifier>(), {\n encode: (v) => v.toString(),\n decode: (v) => PgIdentifier.fromString(v),\n});\n\nexport const FullSchemaKeyColumn = z.object({\n type: z.literal(\"indexColumn\"),\n name: Identifier,\n order: z.enum([\"ASC\", \"DESC\"]).optional(),\n nulls: z.enum([\"FIRST\", \"LAST\"]).optional(),\n opclass: z.string().optional(),\n collation: z.string().optional(),\n});\n\nexport type FullSchemaKeyColumn = z.infer<typeof FullSchemaKeyColumn>;\n\nexport const FullSchemaIncludedColumn = z.object({\n name: Identifier,\n});\n\nexport type FullSchemaIncludedColumn = z.infer<typeof FullSchemaIncludedColumn>;\n\nexport const FullSchemaIndex = z.object({\n type: z.literal(\"index\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n indexName: Identifier,\n indexType: z.string(),\n isUnique: z.boolean(),\n isPrimary: z.boolean(),\n isClustered: z.boolean(),\n wherePredicate: z.string().optional(),\n tablespace: z.string().optional(),\n keyColumns: z.array(FullSchemaKeyColumn),\n includedColumns: z.array(FullSchemaIncludedColumn).optional(),\n});\n\nexport type FullSchemaIndex = z.infer<typeof FullSchemaIndex>;\n\nexport const FullSchemaColumn = z.object({\n type: z.literal(\"column\"),\n name: Identifier,\n order: z.number(),\n columnType: z.string(),\n isNullable: z.boolean(),\n defaultValue: z.string().optional(),\n dropped: z.boolean(),\n collation: z.string().optional(),\n storage: z.enum([\"plain\", \"main\", \"external\", \"extended\"]).optional(),\n isIdentity: z.enum([\"always\", \"by default\"]).optional(),\n});\n\nexport type FullSchemaColumn = z.infer<typeof FullSchemaColumn>;\n\nexport const FullSchemaTable = z.object({\n type: z.literal(\"table\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n tablespace: z.string().optional(),\n partitionKeyDef: z.string().optional(),\n columns: z.array(FullSchemaColumn).default([]),\n});\n\nexport type FullSchemaTable = z.infer<typeof FullSchemaTable>;\n\nexport const FullSchemaConstraint = z.object({\n type: z.literal(\"constraint\"),\n oid: z.number(),\n schemaName: Identifier,\n tableName: Identifier,\n constraintName: Identifier,\n constraintType: z\n .enum([\n \"check\",\n \"foreign_key\",\n \"not_null\",\n \"primary_key\",\n \"unique\",\n \"trigger\",\n \"exclusion\",\n ])\n .or(z.string()),\n definition: z.string(),\n isDeferrable: z.boolean().optional(),\n isInitiallyDeferred: z.boolean().optional(),\n isValidated: z.boolean().optional(),\n backingIndexOid: z.number().optional(),\n});\n\nexport type FullSchemaConstraint = z.infer<typeof FullSchemaConstraint>;\n\nexport const FullSchemaFunction = z.object({\n type: z.literal(\"function\"),\n schemaName: Identifier,\n objectName: Identifier,\n objectType: z.enum([\"function\", \"procedure\", \"aggregate\", \"window function\"]),\n identityArguments: z.string().optional(),\n definition: z.string(),\n});\n\nexport type FullSchemaFunction = z.infer<typeof FullSchemaFunction>;\n\nexport const FullSchemaExtension = z.object({\n type: z.literal(\"extension\"),\n extensionName: z.string(),\n version: z.string(),\n schemaName: Identifier,\n});\n\nexport type FullSchemaExtension = z.infer<typeof FullSchemaExtension>;\n\nexport const FullSchemaView = z.object({\n type: z.literal(\"view\"),\n schemaName: Identifier,\n viewName: Identifier,\n objectType: z.enum([\"view\", \"materialized_view\"]),\n definition: z.string(),\n tablespace: z.string().optional(),\n});\n\nexport type FullSchemaView = z.infer<typeof FullSchemaView>;\n\nexport const FullSchemaTypeConstraint = z.object({\n name: Identifier,\n definition: z.string(),\n});\n\nexport type FullSchemaTypeConstraint = z.infer<typeof FullSchemaTypeConstraint>;\n\nexport const FullSchemaCompositeAttribute = z.object({\n type: z.literal(\"compositeAttribute\"),\n name: Identifier,\n attributeType: z.string(),\n collation: Identifier.optional(),\n});\n\nexport type FullSchemaCompositeAttribute = z.infer<\n typeof FullSchemaCompositeAttribute\n>;\n\nexport const FullSchemaType = z.object({\n type: z.literal(\"type\"),\n schemaName: Identifier,\n typeName: Identifier,\n typeCategory: z.enum([\"enum\", \"domain\", \"composite\"]),\n enumLabels: z.array(z.string()).optional(),\n domainBaseType: z.string().optional(),\n domainIsNotNull: z.boolean().optional(),\n domainDefault: z.string().optional(),\n domainConstraints: z.array(FullSchemaTypeConstraint).optional(),\n compositeAttributes: z.array(FullSchemaCompositeAttribute).optional(),\n});\n\nexport type FullSchemaType = z.infer<typeof FullSchemaType>;\n\nexport const FullSchemaTrigger = z.object({\n type: z.literal(\"trigger\"),\n schemaName: Identifier,\n tableName: Identifier,\n triggerName: Identifier,\n definition: z.string(),\n enabledMode: z.string(),\n});\n\nexport type FullSchemaTrigger = z.infer<typeof FullSchemaTrigger>;\n\nexport const FullSchema = z.object({\n indexes: z.array(FullSchemaIndex).default([]),\n tables: z.array(FullSchemaTable).default([]),\n constraints: z.array(FullSchemaConstraint).default([]),\n functions: z.array(FullSchemaFunction).default([]),\n extensions: z.array(FullSchemaExtension).default([]),\n views: z.array(FullSchemaView).default([]),\n types: z.array(FullSchemaType).default([]),\n triggers: z.array(FullSchemaTrigger).default([]),\n});\n\nexport type FullSchema = z.infer<typeof FullSchema>;\n\nexport async function dumpSchema(db: Postgres): Promise<FullSchema> {\n const rows = await db.exec<{ result: FullSchema }>(SCHEMA_DUMP_SQL);\n return FullSchema.parse(rows[0]?.result ?? {});\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalized-fingerprint.cjs","names":["is"],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n ResTarget: {\n val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n location: -1,\n },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n if (!is(target, \"ResTarget\")) return false;\n const val = target.ResTarget.val;\n if (!val) return false;\n return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n const targetList = select.targetList;\n if (targetList) {\n const survivors = targetList.filter((t) => !isBareColumn(t));\n if (survivors.length > 1) {\n const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n select.targetList = keyed.map((k) => k.t);\n } else {\n select.targetList = survivors.length
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.cjs","names":["is"],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n ResTarget: {\n val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n location: -1,\n },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n if (!is(target, \"ResTarget\")) return false;\n const val = target.ResTarget.val;\n if (!val) return false;\n return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n const targetList = select.targetList;\n if (targetList) {\n const survivors = targetList.filter((t) => !isBareColumn(t));\n if (survivors.length > 1) {\n const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n select.targetList = keyed.map((k) => k.t);\n } else {\n select.targetList = survivors.length\n ? survivors\n : [SELECT_LIST_PLACEHOLDER];\n }\n }\n if (select.larg) normalizeSelect(select.larg);\n if (select.rarg) normalizeSelect(select.rarg);\n}\n\n/**\n * Fingerprint a query while normalizing every SELECT target list in the tree\n * so that the presence and order of bare column references don't affect the\n * result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,\n * subqueries, and set-operation arms. A change to the set of\n * aggregates/expressions, the WHERE, the FROM, etc. still moves it.\n *\n * Takes the already-parsed AST and an injected {@link FingerprintFn} so core\n * needs no parser or fingerprint runtime. The AST is cloned before mutation.\n */\nexport async function normalizedFingerprint(\n ast: ParseResult,\n fingerprint: FingerprintFn,\n): Promise<string> {\n const cloned: ParseResult = JSON.parse(JSON.stringify(ast));\n\n for (const raw of cloned.stmts ?? []) {\n if (!raw.stmt) continue;\n Walker.traverse(raw.stmt, (node) => {\n if (is(node, \"SelectStmt\")) {\n normalizeSelect(node.SelectStmt);\n }\n });\n }\n\n return fingerprint(deparseSync(cloned));\n}\n"],"mappings":";;;;;;AAeA,MAAM,0BAAgC,EACpC,WAAW;CACT,KAAK,EAAE,SAAS;EAAE,MAAM,EAAE,MAAM,GAAG;EAAE,UAAU;EAAI,EAAE;CACrD,UAAU;CACX,EACF;;;;;;AAOD,SAAS,aAAa,QAAuB;AAC3C,KAAI,CAACA,kBAAAA,GAAG,QAAQ,YAAY,CAAE,QAAO;CACrC,MAAM,MAAM,OAAO,UAAU;AAC7B,KAAI,CAAC,IAAK,QAAO;AACjB,QAAOA,kBAAAA,GAAG,KAAK,YAAY;;;;;;;AAQ7B,SAAS,gBAAgB,QAA0B;CACjD,MAAM,aAAa,OAAO;AAC1B,KAAI,YAAY;EACd,MAAM,YAAY,WAAW,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;AAC5D,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,QAAQ,UAAU,KAAK,OAAO;IAAE,MAAA,GAAA,eAAA,aAAiB,EAAE;IAAE;IAAG,EAAE;AAChE,SAAM,MAAM,GAAG,MAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,EAAG;AAClE,UAAO,aAAa,MAAM,KAAK,MAAM,EAAE,EAAE;QAEzC,QAAO,aAAa,UAAU,SAC1B,YACA,CAAC,wBAAwB;;AAGjC,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;AAC7C,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;;;;;;;;;;;;AAa/C,eAAsB,sBACpB,KACA,aACiB;CACjB,MAAM,SAAsB,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAE3D,MAAK,MAAM,OAAO,OAAO,SAAS,EAAE,EAAE;AACpC,MAAI,CAAC,IAAI,KAAM;AACf,iBAAA,OAAO,SAAS,IAAI,OAAO,SAAS;AAClC,OAAIA,kBAAAA,GAAG,MAAM,aAAa,CACxB,iBAAgB,KAAK,WAAW;IAElC;;AAGJ,QAAO,aAAA,GAAA,eAAA,aAAwB,OAAO,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalized-fingerprint.d.cts","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"mappings":";;;;;;;;AAWA;;;KAAY,aAAA,IAAiB,KAAA,aAAkB,OAAA;;
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.d.cts","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"mappings":";;;;;;;;AAWA;;;KAAY,aAAA,IAAiB,KAAA,aAAkB,OAAA;;AAwD/C;;;;;;;;;iBAAsB,qBAAA,CACpB,GAAA,EAAK,WAAA,EACL,WAAA,EAAa,aAAA,GACZ,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalized-fingerprint.d.mts","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"mappings":";;;;;;;;AAWA;;;KAAY,aAAA,IAAiB,KAAA,aAAkB,OAAA;;
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.d.mts","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"mappings":";;;;;;;;AAWA;;;KAAY,aAAA,IAAiB,KAAA,aAAkB,OAAA;;AAwD/C;;;;;;;;;iBAAsB,qBAAA,CACpB,GAAA,EAAK,WAAA,EACL,WAAA,EAAa,aAAA,GACZ,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalized-fingerprint.mjs","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n ResTarget: {\n val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n location: -1,\n },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n if (!is(target, \"ResTarget\")) return false;\n const val = target.ResTarget.val;\n if (!val) return false;\n return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n const targetList = select.targetList;\n if (targetList) {\n const survivors = targetList.filter((t) => !isBareColumn(t));\n if (survivors.length > 1) {\n const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n select.targetList = keyed.map((k) => k.t);\n } else {\n select.targetList = survivors.length
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.mjs","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n ResTarget: {\n val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n location: -1,\n },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n if (!is(target, \"ResTarget\")) return false;\n const val = target.ResTarget.val;\n if (!val) return false;\n return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n const targetList = select.targetList;\n if (targetList) {\n const survivors = targetList.filter((t) => !isBareColumn(t));\n if (survivors.length > 1) {\n const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n select.targetList = keyed.map((k) => k.t);\n } else {\n select.targetList = survivors.length\n ? survivors\n : [SELECT_LIST_PLACEHOLDER];\n }\n }\n if (select.larg) normalizeSelect(select.larg);\n if (select.rarg) normalizeSelect(select.rarg);\n}\n\n/**\n * Fingerprint a query while normalizing every SELECT target list in the tree\n * so that the presence and order of bare column references don't affect the\n * result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,\n * subqueries, and set-operation arms. A change to the set of\n * aggregates/expressions, the WHERE, the FROM, etc. still moves it.\n *\n * Takes the already-parsed AST and an injected {@link FingerprintFn} so core\n * needs no parser or fingerprint runtime. The AST is cloned before mutation.\n */\nexport async function normalizedFingerprint(\n ast: ParseResult,\n fingerprint: FingerprintFn,\n): Promise<string> {\n const cloned: ParseResult = JSON.parse(JSON.stringify(ast));\n\n for (const raw of cloned.stmts ?? []) {\n if (!raw.stmt) continue;\n Walker.traverse(raw.stmt, (node) => {\n if (is(node, \"SelectStmt\")) {\n normalizeSelect(node.SelectStmt);\n }\n });\n }\n\n return fingerprint(deparseSync(cloned));\n}\n"],"mappings":";;;;;AAeA,MAAM,0BAAgC,EACpC,WAAW;CACT,KAAK,EAAE,SAAS;EAAE,MAAM,EAAE,MAAM,GAAG;EAAE,UAAU;EAAI,EAAE;CACrD,UAAU;CACX,EACF;;;;;;AAOD,SAAS,aAAa,QAAuB;AAC3C,KAAI,CAAC,GAAG,QAAQ,YAAY,CAAE,QAAO;CACrC,MAAM,MAAM,OAAO,UAAU;AAC7B,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO,GAAG,KAAK,YAAY;;;;;;;AAQ7B,SAAS,gBAAgB,QAA0B;CACjD,MAAM,aAAa,OAAO;AAC1B,KAAI,YAAY;EACd,MAAM,YAAY,WAAW,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;AAC5D,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,QAAQ,UAAU,KAAK,OAAO;IAAE,KAAK,YAAY,EAAE;IAAE;IAAG,EAAE;AAChE,SAAM,MAAM,GAAG,MAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,EAAG;AAClE,UAAO,aAAa,MAAM,KAAK,MAAM,EAAE,EAAE;QAEzC,QAAO,aAAa,UAAU,SAC1B,YACA,CAAC,wBAAwB;;AAGjC,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;AAC7C,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;;;;;;;;;;;;AAa/C,eAAsB,sBACpB,KACA,aACiB;CACjB,MAAM,SAAsB,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAE3D,MAAK,MAAM,OAAO,OAAO,SAAS,EAAE,EAAE;AACpC,MAAI,CAAC,IAAI,KAAM;AACf,SAAO,SAAS,IAAI,OAAO,SAAS;AAClC,OAAI,GAAG,MAAM,aAAa,CACxB,iBAAgB,KAAK,WAAW;IAElC;;AAGJ,QAAO,YAAY,YAAY,OAAO,CAAC"}
|
package/dist/sql/nudges.cjs
CHANGED
|
@@ -74,7 +74,8 @@ function parseNudges(node, stack) {
|
|
|
74
74
|
message: "Missing WHERE clause",
|
|
75
75
|
location: fromLocation
|
|
76
76
|
});
|
|
77
|
-
|
|
77
|
+
const isBareAggregate = !node.SelectStmt.groupClause?.length && containsAggregate(node.SelectStmt.targetList, true);
|
|
78
|
+
if (!node.SelectStmt.limitCount && !isBareAggregate) nudges.push({
|
|
78
79
|
kind: "MISSING_LIMIT_CLAUSE",
|
|
79
80
|
severity: "INFO",
|
|
80
81
|
message: "Missing LIMIT clause",
|
|
@@ -350,17 +351,18 @@ const AGGREGATE_FUNCTIONS = new Set([
|
|
|
350
351
|
"bool_or",
|
|
351
352
|
"every"
|
|
352
353
|
]);
|
|
353
|
-
function containsAggregate(node) {
|
|
354
|
+
function containsAggregate(node, skipSubqueries = false) {
|
|
354
355
|
if (!node || typeof node !== "object") return false;
|
|
355
|
-
if (Array.isArray(node)) return node.some(containsAggregate);
|
|
356
|
+
if (Array.isArray(node)) return node.some((child) => containsAggregate(child, skipSubqueries));
|
|
357
|
+
if (skipSubqueries && require_ast_utils.isANode(node) && require_ast_utils.is(node, "SubLink")) return false;
|
|
356
358
|
if (require_ast_utils.isANode(node) && require_ast_utils.is(node, "FuncCall")) {
|
|
357
359
|
const funcname = node.FuncCall.funcname;
|
|
358
360
|
if (funcname) {
|
|
359
361
|
for (const f of funcname) if (require_ast_utils.isANode(f) && require_ast_utils.is(f, "String") && AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? "")) return true;
|
|
360
362
|
}
|
|
361
363
|
}
|
|
362
|
-
if (require_ast_utils.isANode(node)) return containsAggregate(node[Object.keys(node)[0]]);
|
|
363
|
-
for (const child of Object.values(node)) if (containsAggregate(child)) return true;
|
|
364
|
+
if (require_ast_utils.isANode(node)) return containsAggregate(node[Object.keys(node)[0]], skipSubqueries);
|
|
365
|
+
for (const child of Object.values(node)) if (containsAggregate(child, skipSubqueries)) return true;
|
|
364
366
|
return false;
|
|
365
367
|
}
|
|
366
368
|
function countBoolOrConditions(node) {
|
package/dist/sql/nudges.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nudges.cjs","names":["is","isANode"],"sources":["../../src/sql/nudges.ts"],"sourcesContent":["import type { A_Const, List, Node, ResTarget } from \"@pgsql/types\";\nimport { is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\ntype NudgeKind =\n | \"LARGE_IMPROVEMENT_FOUND\"\n | \"SMALL_IMPROVEMENT_FOUND\"\n | \"AVOID_SELECT_STAR\"\n | \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\"\n | \"MISSING_WHERE_CLAUSE\"\n | \"MISSING_LIMIT_CLAUSE\"\n | \"USE_IS_NULL_NOT_EQUALS\"\n | \"AVOID_DISTINCT_WITHOUT_REASON\"\n | \"MISSING_JOIN_CONDITION\"\n | \"AVOID_LEADING_WILDCARD_LIKE\"\n | \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\"\n | \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\"\n | \"PREFER_NOT_EXISTS_OVER_NOT_IN\"\n | \"AVOID_ORDER_BY_RANDOM\"\n | \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\"\n | \"PREFER_UNION_ALL_OVER_UNION\"\n | \"NULLS_FIRST_IN_DESC_ORDER\"\n | \"AVOID_SCALAR_SUBQUERY_IN_SELECT\"\n | \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\"\n | \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\"\n | \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\"\n | \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\"\n | \"LIMIT_WITHOUT_ORDER_BY\";\n\nexport type Nudge = {\n kind: NudgeKind;\n severity: \"CRITICAL\" | \"WARNING\" | \"INFO\";\n message: string;\n location?: number;\n};\n\ntype NudgeCreator = (node: Node) => Nudge[];\n\nconst findFuncCallsOnColumns: NudgeCreator = (whereClause) => {\n const nudges: Nudge[] = [];\n Walker.shallowMatch(whereClause, \"FuncCall\", (node) => {\n if (node.FuncCall.args && containsColumnRef(node.FuncCall.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.FuncCall.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"CoalesceExpr\", (node) => {\n if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.CoalesceExpr.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"TypeCast\", (node) => {\n if (node.TypeCast.arg && hasColumnRefInNode(node.TypeCast.arg)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message: \"Avoid using functions on columns in WHERE clause\",\n location: node.TypeCast.location,\n });\n }\n });\n return nudges;\n};\n\n/**\n * Detect nudges for a single node during AST traversal.\n * Returns an array of nudges found for this node.\n */\nexport function parseNudges(\n node: Node,\n stack: (KeysOfUnion<Node> | string)[],\n): Nudge[] {\n const nudges: Nudge[] = [];\n\n if (is(node, \"SelectStmt\")) {\n if (node.SelectStmt.whereClause) {\n nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));\n }\n const star = node.SelectStmt.targetList?.find(\n (target): target is { ResTarget: ResTarget } => {\n if (\n !(\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"ColumnRef\")\n )\n ) {\n return false;\n }\n const fields = target.ResTarget.val.ColumnRef.fields;\n if (!fields?.some((field) => is(field, \"A_Star\"))) {\n return false;\n }\n // Qualified star (e.g. users.*) is deliberate — skip\n if (fields.length > 1) {\n return false;\n }\n return true;\n },\n );\n if (star) {\n // Skip when all FROM items are subqueries — the outer * just\n // passes through whatever the subquery already selected.\n const fromClause = node.SelectStmt.fromClause;\n const allSubselects =\n fromClause &&\n fromClause.length > 0 &&\n fromClause.every((item) => is(item, \"RangeSubselect\"));\n\n if (!allSubselects) {\n nudges.push({\n kind: \"AVOID_SELECT_STAR\",\n severity: \"INFO\",\n message: \"Avoid using SELECT *\",\n location: star.ResTarget.location,\n });\n }\n }\n\n // Detect correlated scalar subqueries in SELECT (N+1 problem)\n for (const target of node.SelectStmt.targetList ?? []) {\n if (\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"SubLink\") &&\n target.ResTarget.val.SubLink.subLinkType === \"EXPR_SUBLINK\"\n ) {\n nudges.push({\n kind: \"AVOID_SCALAR_SUBQUERY_IN_SELECT\",\n severity: \"WARNING\",\n message:\n \"Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN\",\n location: target.ResTarget.val.SubLink.location,\n });\n }\n }\n }\n\n // Detect unbounded queries (missing WHERE or LIMIT on table queries)\n if (is(node, \"SelectStmt\")) {\n // Only check top-level SELECT statements (not subqueries)\n const isSubquery = stack.some(\n (item) =>\n item === \"RangeSubselect\" ||\n item === \"SubLink\" ||\n item === \"CommonTableExpr\",\n );\n\n if (!isSubquery) {\n const hasFromClause =\n node.SelectStmt.fromClause && node.SelectStmt.fromClause.length > 0;\n if (hasFromClause) {\n // Check if this SELECT queries actual tables (not just subqueries or CTEs)\n const hasActualTables = node.SelectStmt.fromClause!.some((fromItem) => {\n return (\n is(fromItem, \"RangeVar\") ||\n (is(fromItem, \"JoinExpr\") && hasActualTablesInJoin(fromItem))\n );\n });\n\n if (hasActualTables) {\n const firstTable = node.SelectStmt.fromClause!.find((item) =>\n is(item, \"RangeVar\"),\n );\n const fromLocation = firstTable?.RangeVar.location;\n\n if (!node.SelectStmt.whereClause) {\n nudges.push({\n kind: \"MISSING_WHERE_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing WHERE clause\",\n location: fromLocation,\n });\n }\n\n if (!node.SelectStmt.limitCount) {\n nudges.push({\n kind: \"MISSING_LIMIT_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing LIMIT clause\",\n location: fromLocation,\n });\n }\n }\n }\n }\n }\n\n // Detect LIMIT without ORDER BY (non-deterministic results)\n // LIMIT ALL parses as limitCount with A_Const.isnull — exclude it\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.limitCount &&\n !isNullConstant(node.SelectStmt.limitCount) &&\n !node.SelectStmt.sortClause\n ) {\n nudges.push({\n kind: \"LIMIT_WITHOUT_ORDER_BY\",\n severity: \"WARNING\",\n message:\n \"LIMIT without ORDER BY returns non-deterministic results — add ORDER BY for predictable output\",\n });\n }\n\n // Detect ORDER BY DESC without explicit NULLS LAST (NULLs sort first by default)\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (!is(sortItem, \"SortBy\")) continue;\n\n const sortDir = sortItem.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\";\n const sortNulls = sortItem.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\";\n\n if (sortDir === \"SORTBY_DESC\" && sortNulls === \"SORTBY_NULLS_DEFAULT\") {\n if (sortItem.SortBy.node && is(sortItem.SortBy.node, \"ColumnRef\")) {\n const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);\n\n const hasIsNotNull =\n sortColumnName !== null &&\n whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName);\n\n if (!hasIsNotNull) {\n nudges.push({\n kind: \"NULLS_FIRST_IN_DESC_ORDER\",\n severity: \"INFO\",\n message:\n \"ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end\",\n location: sortItem.SortBy.node.ColumnRef.location,\n });\n }\n }\n }\n }\n }\n\n // Detect NULL comparison issues (= NULL instead of IS NULL)\n if (is(node, \"A_Expr\")) {\n const isEqualityOp =\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0 &&\n is(node.A_Expr.name[0], \"String\") &&\n (node.A_Expr.name[0].String.sval === \"=\" ||\n node.A_Expr.name[0].String.sval === \"!=\" ||\n node.A_Expr.name[0].String.sval === \"<>\");\n\n if (isEqualityOp) {\n const leftIsNull = isNullConstant(node.A_Expr.lexpr);\n const rightIsNull = isNullConstant(node.A_Expr.rexpr);\n\n if (leftIsNull || rightIsNull) {\n nudges.push({\n kind: \"USE_IS_NULL_NOT_EQUALS\",\n severity: \"WARNING\",\n message: \"Use IS NULL instead of = or != or <> for NULL comparisons\",\n location: node.A_Expr.location,\n });\n }\n }\n\n // Detect LIKE with leading wildcards\n const isLikeOp =\n node.A_Expr.kind === \"AEXPR_LIKE\" || node.A_Expr.kind === \"AEXPR_ILIKE\";\n\n if (isLikeOp && node.A_Expr.rexpr) {\n const patternString = getStringConstantValue(node.A_Expr.rexpr);\n if (patternString && patternString.startsWith(\"%\")) {\n let stringNode: A_Const | undefined;\n if (is(node.A_Expr.rexpr, \"A_Const\")) {\n stringNode = node.A_Expr.rexpr.A_Const;\n }\n nudges.push({\n kind: \"AVOID_LEADING_WILDCARD_LIKE\",\n severity: \"WARNING\",\n message:\n \"Leading wildcard in LIKE/ILIKE prevents index usage — consider a GIN trigram index (pg_trgm) or full-text search\",\n location: stringNode?.location,\n });\n }\n }\n }\n\n // Detect ORDER BY random()\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (\n is(sortItem, \"SortBy\") &&\n sortItem.SortBy.node &&\n is(sortItem.SortBy.node, \"FuncCall\") &&\n sortItem.SortBy.node.FuncCall.funcname?.some(\n (name) => is(name, \"String\") && name.String.sval === \"random\",\n )\n ) {\n nudges.push({\n kind: \"AVOID_ORDER_BY_RANDOM\",\n severity: \"WARNING\",\n message: \"Avoid using ORDER BY random()\",\n location: sortItem.SortBy.node.FuncCall.location,\n });\n }\n }\n }\n\n // Detect DISTINCT usage\n if (is(node, \"SelectStmt\") && node.SelectStmt.distinctClause) {\n nudges.push({\n kind: \"AVOID_DISTINCT_WITHOUT_REASON\",\n severity: \"WARNING\",\n message: \"Avoid using DISTINCT without a reason\",\n });\n }\n\n // Detect cartesian joins (missing JOIN conditions) and functions on columns in ON\n if (is(node, \"JoinExpr\")) {\n if (node.JoinExpr.quals) {\n nudges.push(...findFuncCallsOnColumns(node.JoinExpr.quals));\n } else if (!node.JoinExpr.usingClause) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n });\n }\n }\n\n // Detect multiple tables in FROM without explicit JOINs (old-style cartesian joins)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.fromClause &&\n node.SelectStmt.fromClause.length > 1\n ) {\n // Check if there are multiple RangeVar (tables) directly in FROM clause\n const tables = node.SelectStmt.fromClause.filter((item) =>\n is(item, \"RangeVar\"),\n );\n if (tables.length > 1) {\n // Collect table aliases/names for cross-table equality check\n const tableNames = new Set(\n tables.map(\n (t) => t.RangeVar.alias?.aliasname || t.RangeVar.relname || \"\",\n ),\n );\n\n // Don't fire if WHERE already has a cross-table equality (old-style implicit join)\n if (\n !whereHasCrossTableEquality(node.SelectStmt.whereClause, tableNames)\n ) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n location: tables[1].RangeVar.location,\n });\n }\n }\n }\n\n // Detect too many OR conditions on the same column\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"OR_EXPR\") {\n const orCount = countBoolOrConditions(node);\n if (orCount >= 3 && allOrBranchesReferenceSameColumn(node)) {\n nudges.push({\n kind: \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\",\n severity: \"WARNING\",\n message: \"Consider using IN instead of many ORs\",\n location: node.BoolExpr.location,\n });\n }\n }\n\n // Detect NOT IN (SELECT ...) — prefer NOT EXISTS\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"NOT_EXPR\") {\n const args = node.BoolExpr.args;\n if (args && args.length === 1) {\n const inner = args[0];\n if (\n isANode(inner) &&\n is(inner, \"SubLink\") &&\n inner.SubLink.subLinkType === \"ANY_SUBLINK\"\n ) {\n nudges.push({\n kind: \"PREFER_NOT_EXISTS_OVER_NOT_IN\",\n severity: \"WARNING\",\n message: \"Prefer NOT EXISTS over NOT IN (SELECT ...)\",\n location: inner.SubLink.location,\n });\n }\n }\n }\n\n // Detect COUNT(column) or COUNT(1) — suggest COUNT(*)\n if (is(node, \"FuncCall\")) {\n const funcName = node.FuncCall.funcname;\n const isCount =\n funcName &&\n funcName.length === 1 &&\n is(funcName[0], \"String\") &&\n funcName[0].String.sval === \"count\";\n\n if (\n isCount &&\n node.FuncCall.args &&\n !node.FuncCall.agg_star &&\n !node.FuncCall.agg_distinct\n ) {\n nudges.push({\n kind: \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\",\n severity: \"INFO\",\n message:\n \"Prefer COUNT(*) over COUNT(column) or COUNT(1) — COUNT(*) counts rows without checking for NULLs. If you need to count non-NULL values, COUNT(column) is correct.\",\n location: node.FuncCall.location,\n });\n }\n }\n\n // Detect non-aggregate conditions in HAVING clause\n if (is(node, \"SelectStmt\") && node.SelectStmt.havingClause) {\n if (!containsAggregate(node.SelectStmt.havingClause)) {\n const having = node.SelectStmt.havingClause;\n let location: number | undefined;\n if (is(having, \"A_Expr\")) location = having.A_Expr.location;\n else if (is(having, \"BoolExpr\")) location = having.BoolExpr.location;\n\n nudges.push({\n kind: \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\",\n severity: \"INFO\",\n message: \"Non-aggregate condition in HAVING should be in WHERE\",\n location,\n });\n }\n }\n\n // Detect JSONB set-returning functions in WHERE context\n if (is(node, \"FuncCall\")) {\n const inWhereClause = stack.some((item) => item === \"whereClause\");\n if (inWhereClause && node.FuncCall.args) {\n const name = getFuncName(node);\n if (\n name &&\n JSONB_SET_RETURNING_FUNCTIONS.has(name) &&\n containsColumnRef(node.FuncCall.args)\n ) {\n nudges.push({\n kind: \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\",\n severity: \"INFO\",\n message:\n \"JSONB set-returning functions (e.g. jsonb_array_elements) cannot be used as an index access path. If the query checks for containment or key existence, GIN-compatible operators (@>, ?, ?|, ?&, @?, @@) may allow index usage.\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n\n // Too many parameters in a tuple\n if (is(node, \"A_Expr\")) {\n if (node.A_Expr.kind === \"AEXPR_IN\") {\n let list: List | undefined;\n if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"List\")) {\n list = node.A_Expr.lexpr.List;\n } else if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"List\")) {\n list = node.A_Expr.rexpr.List;\n }\n\n if (list?.items && list.items.length >= 10) {\n nudges.push({\n kind: \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\",\n message:\n \"`in (...)` queries with large tuples can often be replaced with `= ANY($1)` using a single parameter\",\n severity: \"INFO\",\n location: node.A_Expr.location,\n });\n }\n }\n }\n\n // Detect CASE inside aggregate functions (prefer FILTER)\n if (is(node, \"FuncCall\")) {\n const funcname =\n node.FuncCall.funcname?.[0] &&\n is(node.FuncCall.funcname[0], \"String\") &&\n node.FuncCall.funcname[0].String.sval;\n\n if (\n funcname &&\n [\"sum\", \"count\", \"avg\", \"min\", \"max\"].includes(funcname.toLowerCase())\n ) {\n const firstArg = node.FuncCall.args?.[0];\n if (firstArg && isANode(firstArg) && is(firstArg, \"CaseExpr\")) {\n const caseExpr = firstArg.CaseExpr;\n // Only flag simple CASE expressions (single WHEN clause)\n if (caseExpr.args && caseExpr.args.length === 1) {\n // Check ELSE clause: must be absent, ELSE 0, or ELSE NULL\n const defresult = caseExpr.defresult;\n const isSimpleElse =\n !defresult ||\n (isANode(defresult) &&\n is(defresult, \"A_Const\") &&\n (defresult.A_Const.isnull !== undefined ||\n (defresult.A_Const.ival !== undefined &&\n (defresult.A_Const.ival.ival === 0 ||\n defresult.A_Const.ival.ival === undefined))));\n\n if (isSimpleElse) {\n nudges.push({\n kind: \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\",\n severity: \"INFO\",\n message:\n \"Use FILTER (WHERE ...) instead of CASE inside aggregate functions\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n }\n }\n\n // Detect UNION without ALL (implicit deduplication)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.op === \"SETOP_UNION\" &&\n !node.SelectStmt.all\n ) {\n nudges.push({\n kind: \"PREFER_UNION_ALL_OVER_UNION\",\n severity: \"INFO\",\n message:\n \"UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed\",\n });\n }\n\n // Detect COUNT(*)/COUNT(1) compared to 0 or 1 (existence check)\n if (\n is(node, \"A_Expr\") &&\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0\n ) {\n const opNode = node.A_Expr.name[0];\n const op = is(opNode, \"String\") ? opNode.String.sval : null;\n\n if (\n op &&\n isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)\n ) {\n nudges.push({\n kind: \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\",\n severity: \"INFO\",\n message: \"Use EXISTS instead of COUNT for existence checks\",\n location: node.A_Expr.location,\n });\n }\n }\n\n return nudges;\n}\n\nfunction containsColumnRef(args: unknown[]): boolean {\n // Recursively check if any argument contains a ColumnRef\n for (const arg of args) {\n if (hasColumnRefInNode(arg)) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasColumnRefInNode(node: unknown): boolean {\n if (isANode(node) && is(node, \"ColumnRef\")) {\n return true;\n }\n\n if (typeof node !== \"object\" || node === null) {\n return false;\n }\n\n if (Array.isArray(node)) {\n return node.some((item) => hasColumnRefInNode(item));\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return hasColumnRefInNode(node[keys[0]]);\n }\n\n for (const child of Object.values(node)) {\n if (hasColumnRefInNode(child)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasActualTablesInJoin(\n joinExpr: Extract<Node, { JoinExpr: unknown }>,\n): boolean {\n // Check left side of join\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.larg)) {\n return true;\n }\n }\n\n // Check right side of join\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.rarg)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction isNullConstant(node: unknown): boolean {\n if (!node || typeof node !== \"object\") {\n return false;\n }\n\n if (isANode(node) && is(node, \"A_Const\")) {\n // Check if it's a NULL constant\n return node.A_Const.isnull !== undefined;\n }\n\n return false;\n}\n\nfunction getStringConstantValue(node: unknown): string | null {\n if (!node || typeof node !== \"object\") {\n return null;\n }\n\n if (isANode(node) && is(node, \"A_Const\") && node.A_Const.sval) {\n return node.A_Const.sval.sval || null;\n }\n\n return null;\n}\n\nconst JSONB_SET_RETURNING_FUNCTIONS = new Set([\n \"jsonb_array_elements\",\n \"json_array_elements\",\n \"jsonb_array_elements_text\",\n \"json_array_elements_text\",\n \"jsonb_each\",\n \"json_each\",\n \"jsonb_each_text\",\n \"json_each_text\",\n]);\n\nfunction getFuncName(\n node: Extract<Node, { FuncCall: unknown }>,\n): string | null {\n const names = node.FuncCall.funcname;\n if (!names || names.length === 0) return null;\n const last = names[names.length - 1];\n if (isANode(last) && is(last, \"String\") && last.String.sval) {\n return last.String.sval;\n }\n return null;\n}\n\nfunction getLastColumnRefField(\n columnRef: Extract<Node, { ColumnRef: unknown }>,\n): string | null {\n const fields = columnRef.ColumnRef.fields;\n if (!fields || fields.length === 0) return null;\n\n const lastField = fields[fields.length - 1];\n if (isANode(lastField) && is(lastField, \"String\")) {\n return lastField.String.sval || null;\n }\n return null;\n}\n\nfunction whereHasIsNotNull(\n whereClause: Node | undefined,\n columnName: string,\n): boolean {\n if (!whereClause) return false;\n\n let found = false;\n Walker.shallowMatch(whereClause, \"NullTest\", (node) => {\n if (\n node.NullTest.nulltesttype === \"IS_NOT_NULL\" &&\n node.NullTest.arg &&\n is(node.NullTest.arg, \"ColumnRef\")\n ) {\n const name = getLastColumnRefField(node.NullTest.arg);\n if (name === columnName) {\n found = true;\n }\n }\n });\n\n return found;\n}\n\nconst AGGREGATE_FUNCTIONS = new Set([\n \"count\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"array_agg\",\n \"string_agg\",\n \"bool_and\",\n \"bool_or\",\n \"every\",\n]);\n\nfunction containsAggregate(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n\n if (Array.isArray(node)) {\n return node.some(containsAggregate);\n }\n\n if (isANode(node) && is(node, \"FuncCall\")) {\n const funcname = node.FuncCall.funcname;\n if (funcname) {\n for (const f of funcname) {\n if (\n isANode(f) &&\n is(f, \"String\") &&\n AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? \"\")\n ) {\n return true;\n }\n }\n }\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return containsAggregate(node[keys[0]]);\n }\n\n for (const child of Object.values(node)) {\n if (containsAggregate(child)) return true;\n }\n\n return false;\n}\n\nfunction countBoolOrConditions(\n node: Extract<Node, { BoolExpr: unknown }>,\n): number {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return 1;\n }\n\n let count = 0;\n for (const arg of node.BoolExpr.args) {\n if (\n isANode(arg) &&\n is(arg, \"BoolExpr\") &&\n arg.BoolExpr.boolop === \"OR_EXPR\"\n ) {\n count += countBoolOrConditions(arg);\n } else {\n count += 1;\n }\n }\n\n return count;\n}\n\n/**\n * Check whether every leaf of a top-level OR expression references the same\n * left-hand column (e.g. `status = 'a' OR status = 'b' OR status = 'c'`).\n * Returns false when ORs span different columns — IN rewrite doesn't apply.\n */\nfunction allOrBranchesReferenceSameColumn(\n node: Extract<Node, { BoolExpr: unknown }>,\n): boolean {\n const columns = collectOrLeafColumns(node);\n if (columns.length === 0) return false;\n return columns.every((col) => col === columns[0]);\n}\n\nfunction collectOrLeafColumns(\n node: Extract<Node, { BoolExpr: unknown }>,\n): string[] {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return [];\n }\n\n const columns: string[] = [];\n for (const arg of node.BoolExpr.args) {\n if (!isANode(arg)) continue;\n if (is(arg, \"BoolExpr\") && arg.BoolExpr.boolop === \"OR_EXPR\") {\n columns.push(...collectOrLeafColumns(arg));\n } else if (is(arg, \"A_Expr\")) {\n const col = getLeftColumnKey(arg);\n if (col) {\n columns.push(col);\n } else {\n // Non-column comparison — can't be rewritten as IN\n return [];\n }\n } else {\n // Non-A_Expr leaf (e.g. SubLink, BoolExpr AND) — not a simple OR chain\n return [];\n }\n }\n return columns;\n}\n\n/**\n * Get a string key for the left-hand column of an A_Expr equality comparison.\n * For `t1.col = value` returns `\"t1.col\"`, for `col = value` returns `\"col\"`.\n */\nfunction getLeftColumnKey(\n expr: Extract<Node, { A_Expr: unknown }>,\n): string | null {\n if (!expr.A_Expr.lexpr || !isANode(expr.A_Expr.lexpr)) return null;\n if (!is(expr.A_Expr.lexpr, \"ColumnRef\")) return null;\n const fields = expr.A_Expr.lexpr.ColumnRef.fields;\n if (!fields) return null;\n return fields\n .filter(\n (f): f is Extract<Node, { String: unknown }> =>\n isANode(f) && is(f, \"String\"),\n )\n .map((f) => f.String.sval)\n .join(\".\");\n}\n\n/**\n * Check if a WHERE clause contains an equality between columns from different\n * tables (indicating an old-style implicit join condition).\n */\nfunction whereHasCrossTableEquality(\n whereClause: Node | undefined,\n tableNames: Set<string>,\n): boolean {\n if (!whereClause) return false;\n\n if (isANode(whereClause) && is(whereClause, \"A_Expr\")) {\n if (\n whereClause.A_Expr.kind === \"AEXPR_OP\" &&\n whereClause.A_Expr.name?.some(\n (n) => is(n, \"String\") && n.String.sval === \"=\",\n )\n ) {\n const leftTable = getColumnRefTableQualifier(whereClause.A_Expr.lexpr);\n const rightTable = getColumnRefTableQualifier(whereClause.A_Expr.rexpr);\n if (\n leftTable &&\n rightTable &&\n leftTable !== rightTable &&\n tableNames.has(leftTable) &&\n tableNames.has(rightTable)\n ) {\n return true;\n }\n }\n }\n\n // Recurse into AND/OR boolean expressions\n if (isANode(whereClause) && is(whereClause, \"BoolExpr\")) {\n for (const arg of whereClause.BoolExpr.args ?? []) {\n if (isANode(arg) && whereHasCrossTableEquality(arg, tableNames)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Extract the table qualifier (first field) from a ColumnRef node.\n * e.g. `t1.uid` → `\"t1\"`, `uid` → null\n */\nfunction getColumnRefTableQualifier(node: unknown): string | null {\n if (!node || !isANode(node as Node) || !is(node as Node, \"ColumnRef\"))\n return null;\n const fields = (node as Extract<Node, { ColumnRef: unknown }>).ColumnRef\n .fields;\n if (!fields || fields.length < 2) return null;\n const first = fields[0];\n if (isANode(first) && is(first, \"String\")) {\n return first.String.sval || null;\n }\n return null;\n}\n\nfunction isCountFuncCall(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"FuncCall\")) return false;\n const fc = node.FuncCall;\n const isCount =\n fc.funcname?.some((n) => is(n, \"String\") && n.String.sval === \"count\") ??\n false;\n if (!isCount) return false;\n // COUNT(*) has agg_star\n if (fc.agg_star) return true;\n // COUNT(1) or COUNT(literal) — single constant argument\n if (\n fc.args &&\n fc.args.length === 1 &&\n isANode(fc.args[0]) &&\n is(fc.args[0], \"A_Const\")\n ) {\n return true;\n }\n return false;\n}\n\nfunction isSubLinkWithCount(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"SubLink\")) return false;\n const sub = node.SubLink;\n const subselect = sub.subselect;\n if (!subselect || !isANode(subselect) || !is(subselect, \"SelectStmt\"))\n return false;\n const targets = subselect.SelectStmt.targetList;\n if (!targets || targets.length !== 1) return false;\n const target = targets[0];\n if (!isANode(target) || !is(target, \"ResTarget\") || !target.ResTarget.val)\n return false;\n return isCountFuncCall(target.ResTarget.val);\n}\n\nfunction isCountExpression(node: unknown): boolean {\n return isCountFuncCall(node) || isSubLinkWithCount(node);\n}\n\nfunction getIntegerConstantValue(node: unknown): number | null {\n if (!node || typeof node !== \"object\") return null;\n if (!isANode(node) || !is(node, \"A_Const\")) return null;\n if (node.A_Const.ival === undefined) return null;\n // protobuf: ival: {} means 0, ival: { ival: N } means N\n return node.A_Const.ival.ival ?? 0;\n}\n\nfunction isExistenceCheckPattern(\n lexpr: unknown,\n rexpr: unknown,\n op: string,\n): boolean {\n // count_expr > 0, count_expr >= 1, count_expr != 0, count_expr <> 0\n if (isCountExpression(lexpr)) {\n const val = getIntegerConstantValue(rexpr);\n if (val !== null) {\n if (op === \">\" && val === 0) return true;\n if (op === \">=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n // Reversed: 0 < count_expr, 1 <= count_expr, 0 != count_expr\n if (isCountExpression(rexpr)) {\n const val = getIntegerConstantValue(lexpr);\n if (val !== null) {\n if (op === \"<\" && val === 0) return true;\n if (op === \"<=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nudges.cjs","names":["is","isANode"],"sources":["../../src/sql/nudges.ts"],"sourcesContent":["import type { A_Const, List, Node, ResTarget } from \"@pgsql/types\";\nimport { is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\ntype NudgeKind =\n | \"LARGE_IMPROVEMENT_FOUND\"\n | \"SMALL_IMPROVEMENT_FOUND\"\n | \"AVOID_SELECT_STAR\"\n | \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\"\n | \"MISSING_WHERE_CLAUSE\"\n | \"MISSING_LIMIT_CLAUSE\"\n | \"USE_IS_NULL_NOT_EQUALS\"\n | \"AVOID_DISTINCT_WITHOUT_REASON\"\n | \"MISSING_JOIN_CONDITION\"\n | \"AVOID_LEADING_WILDCARD_LIKE\"\n | \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\"\n | \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\"\n | \"PREFER_NOT_EXISTS_OVER_NOT_IN\"\n | \"AVOID_ORDER_BY_RANDOM\"\n | \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\"\n | \"PREFER_UNION_ALL_OVER_UNION\"\n | \"NULLS_FIRST_IN_DESC_ORDER\"\n | \"AVOID_SCALAR_SUBQUERY_IN_SELECT\"\n | \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\"\n | \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\"\n | \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\"\n | \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\"\n | \"LIMIT_WITHOUT_ORDER_BY\";\n\nexport type Nudge = {\n kind: NudgeKind;\n severity: \"CRITICAL\" | \"WARNING\" | \"INFO\";\n message: string;\n location?: number;\n};\n\ntype NudgeCreator = (node: Node) => Nudge[];\n\nconst findFuncCallsOnColumns: NudgeCreator = (whereClause) => {\n const nudges: Nudge[] = [];\n Walker.shallowMatch(whereClause, \"FuncCall\", (node) => {\n if (node.FuncCall.args && containsColumnRef(node.FuncCall.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.FuncCall.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"CoalesceExpr\", (node) => {\n if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.CoalesceExpr.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"TypeCast\", (node) => {\n if (node.TypeCast.arg && hasColumnRefInNode(node.TypeCast.arg)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message: \"Avoid using functions on columns in WHERE clause\",\n location: node.TypeCast.location,\n });\n }\n });\n return nudges;\n};\n\n/**\n * Detect nudges for a single node during AST traversal.\n * Returns an array of nudges found for this node.\n */\nexport function parseNudges(\n node: Node,\n stack: (KeysOfUnion<Node> | string)[],\n): Nudge[] {\n const nudges: Nudge[] = [];\n\n if (is(node, \"SelectStmt\")) {\n if (node.SelectStmt.whereClause) {\n nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));\n }\n const star = node.SelectStmt.targetList?.find(\n (target): target is { ResTarget: ResTarget } => {\n if (\n !(\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"ColumnRef\")\n )\n ) {\n return false;\n }\n const fields = target.ResTarget.val.ColumnRef.fields;\n if (!fields?.some((field) => is(field, \"A_Star\"))) {\n return false;\n }\n // Qualified star (e.g. users.*) is deliberate — skip\n if (fields.length > 1) {\n return false;\n }\n return true;\n },\n );\n if (star) {\n // Skip when all FROM items are subqueries — the outer * just\n // passes through whatever the subquery already selected.\n const fromClause = node.SelectStmt.fromClause;\n const allSubselects =\n fromClause &&\n fromClause.length > 0 &&\n fromClause.every((item) => is(item, \"RangeSubselect\"));\n\n if (!allSubselects) {\n nudges.push({\n kind: \"AVOID_SELECT_STAR\",\n severity: \"INFO\",\n message: \"Avoid using SELECT *\",\n location: star.ResTarget.location,\n });\n }\n }\n\n // Detect correlated scalar subqueries in SELECT (N+1 problem)\n for (const target of node.SelectStmt.targetList ?? []) {\n if (\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"SubLink\") &&\n target.ResTarget.val.SubLink.subLinkType === \"EXPR_SUBLINK\"\n ) {\n nudges.push({\n kind: \"AVOID_SCALAR_SUBQUERY_IN_SELECT\",\n severity: \"WARNING\",\n message:\n \"Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN\",\n location: target.ResTarget.val.SubLink.location,\n });\n }\n }\n }\n\n // Detect unbounded queries (missing WHERE or LIMIT on table queries)\n if (is(node, \"SelectStmt\")) {\n // Only check top-level SELECT statements (not subqueries)\n const isSubquery = stack.some(\n (item) =>\n item === \"RangeSubselect\" ||\n item === \"SubLink\" ||\n item === \"CommonTableExpr\",\n );\n\n if (!isSubquery) {\n const hasFromClause =\n node.SelectStmt.fromClause && node.SelectStmt.fromClause.length > 0;\n if (hasFromClause) {\n // Check if this SELECT queries actual tables (not just subqueries or CTEs)\n const hasActualTables = node.SelectStmt.fromClause!.some((fromItem) => {\n return (\n is(fromItem, \"RangeVar\") ||\n (is(fromItem, \"JoinExpr\") && hasActualTablesInJoin(fromItem))\n );\n });\n\n if (hasActualTables) {\n const firstTable = node.SelectStmt.fromClause!.find((item) =>\n is(item, \"RangeVar\"),\n );\n const fromLocation = firstTable?.RangeVar.location;\n\n if (!node.SelectStmt.whereClause) {\n nudges.push({\n kind: \"MISSING_WHERE_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing WHERE clause\",\n location: fromLocation,\n });\n }\n\n // A bare aggregate select (aggregates in the target list, no GROUP BY)\n // returns exactly one row, so a LIMIT adds nothing. Aggregates with\n // GROUP BY can still return many rows and stay eligible.\n const isBareAggregate =\n !node.SelectStmt.groupClause?.length &&\n containsAggregate(node.SelectStmt.targetList, true);\n\n if (!node.SelectStmt.limitCount && !isBareAggregate) {\n nudges.push({\n kind: \"MISSING_LIMIT_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing LIMIT clause\",\n location: fromLocation,\n });\n }\n }\n }\n }\n }\n\n // Detect LIMIT without ORDER BY (non-deterministic results)\n // LIMIT ALL parses as limitCount with A_Const.isnull — exclude it\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.limitCount &&\n !isNullConstant(node.SelectStmt.limitCount) &&\n !node.SelectStmt.sortClause\n ) {\n nudges.push({\n kind: \"LIMIT_WITHOUT_ORDER_BY\",\n severity: \"WARNING\",\n message:\n \"LIMIT without ORDER BY returns non-deterministic results — add ORDER BY for predictable output\",\n });\n }\n\n // Detect ORDER BY DESC without explicit NULLS LAST (NULLs sort first by default)\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (!is(sortItem, \"SortBy\")) continue;\n\n const sortDir = sortItem.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\";\n const sortNulls = sortItem.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\";\n\n if (sortDir === \"SORTBY_DESC\" && sortNulls === \"SORTBY_NULLS_DEFAULT\") {\n if (sortItem.SortBy.node && is(sortItem.SortBy.node, \"ColumnRef\")) {\n const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);\n\n const hasIsNotNull =\n sortColumnName !== null &&\n whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName);\n\n if (!hasIsNotNull) {\n nudges.push({\n kind: \"NULLS_FIRST_IN_DESC_ORDER\",\n severity: \"INFO\",\n message:\n \"ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end\",\n location: sortItem.SortBy.node.ColumnRef.location,\n });\n }\n }\n }\n }\n }\n\n // Detect NULL comparison issues (= NULL instead of IS NULL)\n if (is(node, \"A_Expr\")) {\n const isEqualityOp =\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0 &&\n is(node.A_Expr.name[0], \"String\") &&\n (node.A_Expr.name[0].String.sval === \"=\" ||\n node.A_Expr.name[0].String.sval === \"!=\" ||\n node.A_Expr.name[0].String.sval === \"<>\");\n\n if (isEqualityOp) {\n const leftIsNull = isNullConstant(node.A_Expr.lexpr);\n const rightIsNull = isNullConstant(node.A_Expr.rexpr);\n\n if (leftIsNull || rightIsNull) {\n nudges.push({\n kind: \"USE_IS_NULL_NOT_EQUALS\",\n severity: \"WARNING\",\n message: \"Use IS NULL instead of = or != or <> for NULL comparisons\",\n location: node.A_Expr.location,\n });\n }\n }\n\n // Detect LIKE with leading wildcards\n const isLikeOp =\n node.A_Expr.kind === \"AEXPR_LIKE\" || node.A_Expr.kind === \"AEXPR_ILIKE\";\n\n if (isLikeOp && node.A_Expr.rexpr) {\n const patternString = getStringConstantValue(node.A_Expr.rexpr);\n if (patternString && patternString.startsWith(\"%\")) {\n let stringNode: A_Const | undefined;\n if (is(node.A_Expr.rexpr, \"A_Const\")) {\n stringNode = node.A_Expr.rexpr.A_Const;\n }\n nudges.push({\n kind: \"AVOID_LEADING_WILDCARD_LIKE\",\n severity: \"WARNING\",\n message:\n \"Leading wildcard in LIKE/ILIKE prevents index usage — consider a GIN trigram index (pg_trgm) or full-text search\",\n location: stringNode?.location,\n });\n }\n }\n }\n\n // Detect ORDER BY random()\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (\n is(sortItem, \"SortBy\") &&\n sortItem.SortBy.node &&\n is(sortItem.SortBy.node, \"FuncCall\") &&\n sortItem.SortBy.node.FuncCall.funcname?.some(\n (name) => is(name, \"String\") && name.String.sval === \"random\",\n )\n ) {\n nudges.push({\n kind: \"AVOID_ORDER_BY_RANDOM\",\n severity: \"WARNING\",\n message: \"Avoid using ORDER BY random()\",\n location: sortItem.SortBy.node.FuncCall.location,\n });\n }\n }\n }\n\n // Detect DISTINCT usage\n if (is(node, \"SelectStmt\") && node.SelectStmt.distinctClause) {\n nudges.push({\n kind: \"AVOID_DISTINCT_WITHOUT_REASON\",\n severity: \"WARNING\",\n message: \"Avoid using DISTINCT without a reason\",\n });\n }\n\n // Detect cartesian joins (missing JOIN conditions) and functions on columns in ON\n if (is(node, \"JoinExpr\")) {\n if (node.JoinExpr.quals) {\n nudges.push(...findFuncCallsOnColumns(node.JoinExpr.quals));\n } else if (!node.JoinExpr.usingClause) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n });\n }\n }\n\n // Detect multiple tables in FROM without explicit JOINs (old-style cartesian joins)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.fromClause &&\n node.SelectStmt.fromClause.length > 1\n ) {\n // Check if there are multiple RangeVar (tables) directly in FROM clause\n const tables = node.SelectStmt.fromClause.filter((item) =>\n is(item, \"RangeVar\"),\n );\n if (tables.length > 1) {\n // Collect table aliases/names for cross-table equality check\n const tableNames = new Set(\n tables.map(\n (t) => t.RangeVar.alias?.aliasname || t.RangeVar.relname || \"\",\n ),\n );\n\n // Don't fire if WHERE already has a cross-table equality (old-style implicit join)\n if (\n !whereHasCrossTableEquality(node.SelectStmt.whereClause, tableNames)\n ) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n location: tables[1].RangeVar.location,\n });\n }\n }\n }\n\n // Detect too many OR conditions on the same column\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"OR_EXPR\") {\n const orCount = countBoolOrConditions(node);\n if (orCount >= 3 && allOrBranchesReferenceSameColumn(node)) {\n nudges.push({\n kind: \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\",\n severity: \"WARNING\",\n message: \"Consider using IN instead of many ORs\",\n location: node.BoolExpr.location,\n });\n }\n }\n\n // Detect NOT IN (SELECT ...) — prefer NOT EXISTS\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"NOT_EXPR\") {\n const args = node.BoolExpr.args;\n if (args && args.length === 1) {\n const inner = args[0];\n if (\n isANode(inner) &&\n is(inner, \"SubLink\") &&\n inner.SubLink.subLinkType === \"ANY_SUBLINK\"\n ) {\n nudges.push({\n kind: \"PREFER_NOT_EXISTS_OVER_NOT_IN\",\n severity: \"WARNING\",\n message: \"Prefer NOT EXISTS over NOT IN (SELECT ...)\",\n location: inner.SubLink.location,\n });\n }\n }\n }\n\n // Detect COUNT(column) or COUNT(1) — suggest COUNT(*)\n if (is(node, \"FuncCall\")) {\n const funcName = node.FuncCall.funcname;\n const isCount =\n funcName &&\n funcName.length === 1 &&\n is(funcName[0], \"String\") &&\n funcName[0].String.sval === \"count\";\n\n if (\n isCount &&\n node.FuncCall.args &&\n !node.FuncCall.agg_star &&\n !node.FuncCall.agg_distinct\n ) {\n nudges.push({\n kind: \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\",\n severity: \"INFO\",\n message:\n \"Prefer COUNT(*) over COUNT(column) or COUNT(1) — COUNT(*) counts rows without checking for NULLs. If you need to count non-NULL values, COUNT(column) is correct.\",\n location: node.FuncCall.location,\n });\n }\n }\n\n // Detect non-aggregate conditions in HAVING clause\n if (is(node, \"SelectStmt\") && node.SelectStmt.havingClause) {\n if (!containsAggregate(node.SelectStmt.havingClause)) {\n const having = node.SelectStmt.havingClause;\n let location: number | undefined;\n if (is(having, \"A_Expr\")) location = having.A_Expr.location;\n else if (is(having, \"BoolExpr\")) location = having.BoolExpr.location;\n\n nudges.push({\n kind: \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\",\n severity: \"INFO\",\n message: \"Non-aggregate condition in HAVING should be in WHERE\",\n location,\n });\n }\n }\n\n // Detect JSONB set-returning functions in WHERE context\n if (is(node, \"FuncCall\")) {\n const inWhereClause = stack.some((item) => item === \"whereClause\");\n if (inWhereClause && node.FuncCall.args) {\n const name = getFuncName(node);\n if (\n name &&\n JSONB_SET_RETURNING_FUNCTIONS.has(name) &&\n containsColumnRef(node.FuncCall.args)\n ) {\n nudges.push({\n kind: \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\",\n severity: \"INFO\",\n message:\n \"JSONB set-returning functions (e.g. jsonb_array_elements) cannot be used as an index access path. If the query checks for containment or key existence, GIN-compatible operators (@>, ?, ?|, ?&, @?, @@) may allow index usage.\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n\n // Too many parameters in a tuple\n if (is(node, \"A_Expr\")) {\n if (node.A_Expr.kind === \"AEXPR_IN\") {\n let list: List | undefined;\n if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"List\")) {\n list = node.A_Expr.lexpr.List;\n } else if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"List\")) {\n list = node.A_Expr.rexpr.List;\n }\n\n if (list?.items && list.items.length >= 10) {\n nudges.push({\n kind: \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\",\n message:\n \"`in (...)` queries with large tuples can often be replaced with `= ANY($1)` using a single parameter\",\n severity: \"INFO\",\n location: node.A_Expr.location,\n });\n }\n }\n }\n\n // Detect CASE inside aggregate functions (prefer FILTER)\n if (is(node, \"FuncCall\")) {\n const funcname =\n node.FuncCall.funcname?.[0] &&\n is(node.FuncCall.funcname[0], \"String\") &&\n node.FuncCall.funcname[0].String.sval;\n\n if (\n funcname &&\n [\"sum\", \"count\", \"avg\", \"min\", \"max\"].includes(funcname.toLowerCase())\n ) {\n const firstArg = node.FuncCall.args?.[0];\n if (firstArg && isANode(firstArg) && is(firstArg, \"CaseExpr\")) {\n const caseExpr = firstArg.CaseExpr;\n // Only flag simple CASE expressions (single WHEN clause)\n if (caseExpr.args && caseExpr.args.length === 1) {\n // Check ELSE clause: must be absent, ELSE 0, or ELSE NULL\n const defresult = caseExpr.defresult;\n const isSimpleElse =\n !defresult ||\n (isANode(defresult) &&\n is(defresult, \"A_Const\") &&\n (defresult.A_Const.isnull !== undefined ||\n (defresult.A_Const.ival !== undefined &&\n (defresult.A_Const.ival.ival === 0 ||\n defresult.A_Const.ival.ival === undefined))));\n\n if (isSimpleElse) {\n nudges.push({\n kind: \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\",\n severity: \"INFO\",\n message:\n \"Use FILTER (WHERE ...) instead of CASE inside aggregate functions\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n }\n }\n\n // Detect UNION without ALL (implicit deduplication)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.op === \"SETOP_UNION\" &&\n !node.SelectStmt.all\n ) {\n nudges.push({\n kind: \"PREFER_UNION_ALL_OVER_UNION\",\n severity: \"INFO\",\n message:\n \"UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed\",\n });\n }\n\n // Detect COUNT(*)/COUNT(1) compared to 0 or 1 (existence check)\n if (\n is(node, \"A_Expr\") &&\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0\n ) {\n const opNode = node.A_Expr.name[0];\n const op = is(opNode, \"String\") ? opNode.String.sval : null;\n\n if (\n op &&\n isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)\n ) {\n nudges.push({\n kind: \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\",\n severity: \"INFO\",\n message: \"Use EXISTS instead of COUNT for existence checks\",\n location: node.A_Expr.location,\n });\n }\n }\n\n return nudges;\n}\n\nfunction containsColumnRef(args: unknown[]): boolean {\n // Recursively check if any argument contains a ColumnRef\n for (const arg of args) {\n if (hasColumnRefInNode(arg)) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasColumnRefInNode(node: unknown): boolean {\n if (isANode(node) && is(node, \"ColumnRef\")) {\n return true;\n }\n\n if (typeof node !== \"object\" || node === null) {\n return false;\n }\n\n if (Array.isArray(node)) {\n return node.some((item) => hasColumnRefInNode(item));\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return hasColumnRefInNode(node[keys[0]]);\n }\n\n for (const child of Object.values(node)) {\n if (hasColumnRefInNode(child)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasActualTablesInJoin(\n joinExpr: Extract<Node, { JoinExpr: unknown }>,\n): boolean {\n // Check left side of join\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.larg)) {\n return true;\n }\n }\n\n // Check right side of join\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.rarg)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction isNullConstant(node: unknown): boolean {\n if (!node || typeof node !== \"object\") {\n return false;\n }\n\n if (isANode(node) && is(node, \"A_Const\")) {\n // Check if it's a NULL constant\n return node.A_Const.isnull !== undefined;\n }\n\n return false;\n}\n\nfunction getStringConstantValue(node: unknown): string | null {\n if (!node || typeof node !== \"object\") {\n return null;\n }\n\n if (isANode(node) && is(node, \"A_Const\") && node.A_Const.sval) {\n return node.A_Const.sval.sval || null;\n }\n\n return null;\n}\n\nconst JSONB_SET_RETURNING_FUNCTIONS = new Set([\n \"jsonb_array_elements\",\n \"json_array_elements\",\n \"jsonb_array_elements_text\",\n \"json_array_elements_text\",\n \"jsonb_each\",\n \"json_each\",\n \"jsonb_each_text\",\n \"json_each_text\",\n]);\n\nfunction getFuncName(\n node: Extract<Node, { FuncCall: unknown }>,\n): string | null {\n const names = node.FuncCall.funcname;\n if (!names || names.length === 0) return null;\n const last = names[names.length - 1];\n if (isANode(last) && is(last, \"String\") && last.String.sval) {\n return last.String.sval;\n }\n return null;\n}\n\nfunction getLastColumnRefField(\n columnRef: Extract<Node, { ColumnRef: unknown }>,\n): string | null {\n const fields = columnRef.ColumnRef.fields;\n if (!fields || fields.length === 0) return null;\n\n const lastField = fields[fields.length - 1];\n if (isANode(lastField) && is(lastField, \"String\")) {\n return lastField.String.sval || null;\n }\n return null;\n}\n\nfunction whereHasIsNotNull(\n whereClause: Node | undefined,\n columnName: string,\n): boolean {\n if (!whereClause) return false;\n\n let found = false;\n Walker.shallowMatch(whereClause, \"NullTest\", (node) => {\n if (\n node.NullTest.nulltesttype === \"IS_NOT_NULL\" &&\n node.NullTest.arg &&\n is(node.NullTest.arg, \"ColumnRef\")\n ) {\n const name = getLastColumnRefField(node.NullTest.arg);\n if (name === columnName) {\n found = true;\n }\n }\n });\n\n return found;\n}\n\nconst AGGREGATE_FUNCTIONS = new Set([\n \"count\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"array_agg\",\n \"string_agg\",\n \"bool_and\",\n \"bool_or\",\n \"every\",\n]);\n\nfunction containsAggregate(node: unknown, skipSubqueries = false): boolean {\n if (!node || typeof node !== \"object\") return false;\n\n if (Array.isArray(node)) {\n return node.some((child) => containsAggregate(child, skipSubqueries));\n }\n\n // A subquery's aggregate doesn't bound the enclosing query, so callers that\n // reason about the enclosing statement (e.g. bare-aggregate detection) skip it.\n if (skipSubqueries && isANode(node) && is(node, \"SubLink\")) {\n return false;\n }\n\n if (isANode(node) && is(node, \"FuncCall\")) {\n const funcname = node.FuncCall.funcname;\n if (funcname) {\n for (const f of funcname) {\n if (\n isANode(f) &&\n is(f, \"String\") &&\n AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? \"\")\n ) {\n return true;\n }\n }\n }\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return containsAggregate(node[keys[0]], skipSubqueries);\n }\n\n for (const child of Object.values(node)) {\n if (containsAggregate(child, skipSubqueries)) return true;\n }\n\n return false;\n}\n\nfunction countBoolOrConditions(\n node: Extract<Node, { BoolExpr: unknown }>,\n): number {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return 1;\n }\n\n let count = 0;\n for (const arg of node.BoolExpr.args) {\n if (\n isANode(arg) &&\n is(arg, \"BoolExpr\") &&\n arg.BoolExpr.boolop === \"OR_EXPR\"\n ) {\n count += countBoolOrConditions(arg);\n } else {\n count += 1;\n }\n }\n\n return count;\n}\n\n/**\n * Check whether every leaf of a top-level OR expression references the same\n * left-hand column (e.g. `status = 'a' OR status = 'b' OR status = 'c'`).\n * Returns false when ORs span different columns — IN rewrite doesn't apply.\n */\nfunction allOrBranchesReferenceSameColumn(\n node: Extract<Node, { BoolExpr: unknown }>,\n): boolean {\n const columns = collectOrLeafColumns(node);\n if (columns.length === 0) return false;\n return columns.every((col) => col === columns[0]);\n}\n\nfunction collectOrLeafColumns(\n node: Extract<Node, { BoolExpr: unknown }>,\n): string[] {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return [];\n }\n\n const columns: string[] = [];\n for (const arg of node.BoolExpr.args) {\n if (!isANode(arg)) continue;\n if (is(arg, \"BoolExpr\") && arg.BoolExpr.boolop === \"OR_EXPR\") {\n columns.push(...collectOrLeafColumns(arg));\n } else if (is(arg, \"A_Expr\")) {\n const col = getLeftColumnKey(arg);\n if (col) {\n columns.push(col);\n } else {\n // Non-column comparison — can't be rewritten as IN\n return [];\n }\n } else {\n // Non-A_Expr leaf (e.g. SubLink, BoolExpr AND) — not a simple OR chain\n return [];\n }\n }\n return columns;\n}\n\n/**\n * Get a string key for the left-hand column of an A_Expr equality comparison.\n * For `t1.col = value` returns `\"t1.col\"`, for `col = value` returns `\"col\"`.\n */\nfunction getLeftColumnKey(\n expr: Extract<Node, { A_Expr: unknown }>,\n): string | null {\n if (!expr.A_Expr.lexpr || !isANode(expr.A_Expr.lexpr)) return null;\n if (!is(expr.A_Expr.lexpr, \"ColumnRef\")) return null;\n const fields = expr.A_Expr.lexpr.ColumnRef.fields;\n if (!fields) return null;\n return fields\n .filter(\n (f): f is Extract<Node, { String: unknown }> =>\n isANode(f) && is(f, \"String\"),\n )\n .map((f) => f.String.sval)\n .join(\".\");\n}\n\n/**\n * Check if a WHERE clause contains an equality between columns from different\n * tables (indicating an old-style implicit join condition).\n */\nfunction whereHasCrossTableEquality(\n whereClause: Node | undefined,\n tableNames: Set<string>,\n): boolean {\n if (!whereClause) return false;\n\n if (isANode(whereClause) && is(whereClause, \"A_Expr\")) {\n if (\n whereClause.A_Expr.kind === \"AEXPR_OP\" &&\n whereClause.A_Expr.name?.some(\n (n) => is(n, \"String\") && n.String.sval === \"=\",\n )\n ) {\n const leftTable = getColumnRefTableQualifier(whereClause.A_Expr.lexpr);\n const rightTable = getColumnRefTableQualifier(whereClause.A_Expr.rexpr);\n if (\n leftTable &&\n rightTable &&\n leftTable !== rightTable &&\n tableNames.has(leftTable) &&\n tableNames.has(rightTable)\n ) {\n return true;\n }\n }\n }\n\n // Recurse into AND/OR boolean expressions\n if (isANode(whereClause) && is(whereClause, \"BoolExpr\")) {\n for (const arg of whereClause.BoolExpr.args ?? []) {\n if (isANode(arg) && whereHasCrossTableEquality(arg, tableNames)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Extract the table qualifier (first field) from a ColumnRef node.\n * e.g. `t1.uid` → `\"t1\"`, `uid` → null\n */\nfunction getColumnRefTableQualifier(node: unknown): string | null {\n if (!node || !isANode(node as Node) || !is(node as Node, \"ColumnRef\"))\n return null;\n const fields = (node as Extract<Node, { ColumnRef: unknown }>).ColumnRef\n .fields;\n if (!fields || fields.length < 2) return null;\n const first = fields[0];\n if (isANode(first) && is(first, \"String\")) {\n return first.String.sval || null;\n }\n return null;\n}\n\nfunction isCountFuncCall(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"FuncCall\")) return false;\n const fc = node.FuncCall;\n const isCount =\n fc.funcname?.some((n) => is(n, \"String\") && n.String.sval === \"count\") ??\n false;\n if (!isCount) return false;\n // COUNT(*) has agg_star\n if (fc.agg_star) return true;\n // COUNT(1) or COUNT(literal) — single constant argument\n if (\n fc.args &&\n fc.args.length === 1 &&\n isANode(fc.args[0]) &&\n is(fc.args[0], \"A_Const\")\n ) {\n return true;\n }\n return false;\n}\n\nfunction isSubLinkWithCount(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"SubLink\")) return false;\n const sub = node.SubLink;\n const subselect = sub.subselect;\n if (!subselect || !isANode(subselect) || !is(subselect, \"SelectStmt\"))\n return false;\n const targets = subselect.SelectStmt.targetList;\n if (!targets || targets.length !== 1) return false;\n const target = targets[0];\n if (!isANode(target) || !is(target, \"ResTarget\") || !target.ResTarget.val)\n return false;\n return isCountFuncCall(target.ResTarget.val);\n}\n\nfunction isCountExpression(node: unknown): boolean {\n return isCountFuncCall(node) || isSubLinkWithCount(node);\n}\n\nfunction getIntegerConstantValue(node: unknown): number | null {\n if (!node || typeof node !== \"object\") return null;\n if (!isANode(node) || !is(node, \"A_Const\")) return null;\n if (node.A_Const.ival === undefined) return null;\n // protobuf: ival: {} means 0, ival: { ival: N } means N\n return node.A_Const.ival.ival ?? 0;\n}\n\nfunction isExistenceCheckPattern(\n lexpr: unknown,\n rexpr: unknown,\n op: string,\n): boolean {\n // count_expr > 0, count_expr >= 1, count_expr != 0, count_expr <> 0\n if (isCountExpression(lexpr)) {\n const val = getIntegerConstantValue(rexpr);\n if (val !== null) {\n if (op === \">\" && val === 0) return true;\n if (op === \">=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n // Reversed: 0 < count_expr, 1 <= count_expr, 0 != count_expr\n if (isCountExpression(rexpr)) {\n const val = getIntegerConstantValue(lexpr);\n if (val !== null) {\n if (op === \"<\" && val === 0) return true;\n if (op === \"<=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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"}
|
package/dist/sql/nudges.mjs
CHANGED
|
@@ -74,7 +74,8 @@ function parseNudges(node, stack) {
|
|
|
74
74
|
message: "Missing WHERE clause",
|
|
75
75
|
location: fromLocation
|
|
76
76
|
});
|
|
77
|
-
|
|
77
|
+
const isBareAggregate = !node.SelectStmt.groupClause?.length && containsAggregate(node.SelectStmt.targetList, true);
|
|
78
|
+
if (!node.SelectStmt.limitCount && !isBareAggregate) nudges.push({
|
|
78
79
|
kind: "MISSING_LIMIT_CLAUSE",
|
|
79
80
|
severity: "INFO",
|
|
80
81
|
message: "Missing LIMIT clause",
|
|
@@ -350,17 +351,18 @@ const AGGREGATE_FUNCTIONS = new Set([
|
|
|
350
351
|
"bool_or",
|
|
351
352
|
"every"
|
|
352
353
|
]);
|
|
353
|
-
function containsAggregate(node) {
|
|
354
|
+
function containsAggregate(node, skipSubqueries = false) {
|
|
354
355
|
if (!node || typeof node !== "object") return false;
|
|
355
|
-
if (Array.isArray(node)) return node.some(containsAggregate);
|
|
356
|
+
if (Array.isArray(node)) return node.some((child) => containsAggregate(child, skipSubqueries));
|
|
357
|
+
if (skipSubqueries && isANode(node) && is(node, "SubLink")) return false;
|
|
356
358
|
if (isANode(node) && is(node, "FuncCall")) {
|
|
357
359
|
const funcname = node.FuncCall.funcname;
|
|
358
360
|
if (funcname) {
|
|
359
361
|
for (const f of funcname) if (isANode(f) && is(f, "String") && AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? "")) return true;
|
|
360
362
|
}
|
|
361
363
|
}
|
|
362
|
-
if (isANode(node)) return containsAggregate(node[Object.keys(node)[0]]);
|
|
363
|
-
for (const child of Object.values(node)) if (containsAggregate(child)) return true;
|
|
364
|
+
if (isANode(node)) return containsAggregate(node[Object.keys(node)[0]], skipSubqueries);
|
|
365
|
+
for (const child of Object.values(node)) if (containsAggregate(child, skipSubqueries)) return true;
|
|
364
366
|
return false;
|
|
365
367
|
}
|
|
366
368
|
function countBoolOrConditions(node) {
|
package/dist/sql/nudges.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nudges.mjs","names":[],"sources":["../../src/sql/nudges.ts"],"sourcesContent":["import type { A_Const, List, Node, ResTarget } from \"@pgsql/types\";\nimport { is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\ntype NudgeKind =\n | \"LARGE_IMPROVEMENT_FOUND\"\n | \"SMALL_IMPROVEMENT_FOUND\"\n | \"AVOID_SELECT_STAR\"\n | \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\"\n | \"MISSING_WHERE_CLAUSE\"\n | \"MISSING_LIMIT_CLAUSE\"\n | \"USE_IS_NULL_NOT_EQUALS\"\n | \"AVOID_DISTINCT_WITHOUT_REASON\"\n | \"MISSING_JOIN_CONDITION\"\n | \"AVOID_LEADING_WILDCARD_LIKE\"\n | \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\"\n | \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\"\n | \"PREFER_NOT_EXISTS_OVER_NOT_IN\"\n | \"AVOID_ORDER_BY_RANDOM\"\n | \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\"\n | \"PREFER_UNION_ALL_OVER_UNION\"\n | \"NULLS_FIRST_IN_DESC_ORDER\"\n | \"AVOID_SCALAR_SUBQUERY_IN_SELECT\"\n | \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\"\n | \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\"\n | \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\"\n | \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\"\n | \"LIMIT_WITHOUT_ORDER_BY\";\n\nexport type Nudge = {\n kind: NudgeKind;\n severity: \"CRITICAL\" | \"WARNING\" | \"INFO\";\n message: string;\n location?: number;\n};\n\ntype NudgeCreator = (node: Node) => Nudge[];\n\nconst findFuncCallsOnColumns: NudgeCreator = (whereClause) => {\n const nudges: Nudge[] = [];\n Walker.shallowMatch(whereClause, \"FuncCall\", (node) => {\n if (node.FuncCall.args && containsColumnRef(node.FuncCall.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.FuncCall.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"CoalesceExpr\", (node) => {\n if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.CoalesceExpr.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"TypeCast\", (node) => {\n if (node.TypeCast.arg && hasColumnRefInNode(node.TypeCast.arg)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message: \"Avoid using functions on columns in WHERE clause\",\n location: node.TypeCast.location,\n });\n }\n });\n return nudges;\n};\n\n/**\n * Detect nudges for a single node during AST traversal.\n * Returns an array of nudges found for this node.\n */\nexport function parseNudges(\n node: Node,\n stack: (KeysOfUnion<Node> | string)[],\n): Nudge[] {\n const nudges: Nudge[] = [];\n\n if (is(node, \"SelectStmt\")) {\n if (node.SelectStmt.whereClause) {\n nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));\n }\n const star = node.SelectStmt.targetList?.find(\n (target): target is { ResTarget: ResTarget } => {\n if (\n !(\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"ColumnRef\")\n )\n ) {\n return false;\n }\n const fields = target.ResTarget.val.ColumnRef.fields;\n if (!fields?.some((field) => is(field, \"A_Star\"))) {\n return false;\n }\n // Qualified star (e.g. users.*) is deliberate — skip\n if (fields.length > 1) {\n return false;\n }\n return true;\n },\n );\n if (star) {\n // Skip when all FROM items are subqueries — the outer * just\n // passes through whatever the subquery already selected.\n const fromClause = node.SelectStmt.fromClause;\n const allSubselects =\n fromClause &&\n fromClause.length > 0 &&\n fromClause.every((item) => is(item, \"RangeSubselect\"));\n\n if (!allSubselects) {\n nudges.push({\n kind: \"AVOID_SELECT_STAR\",\n severity: \"INFO\",\n message: \"Avoid using SELECT *\",\n location: star.ResTarget.location,\n });\n }\n }\n\n // Detect correlated scalar subqueries in SELECT (N+1 problem)\n for (const target of node.SelectStmt.targetList ?? []) {\n if (\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"SubLink\") &&\n target.ResTarget.val.SubLink.subLinkType === \"EXPR_SUBLINK\"\n ) {\n nudges.push({\n kind: \"AVOID_SCALAR_SUBQUERY_IN_SELECT\",\n severity: \"WARNING\",\n message:\n \"Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN\",\n location: target.ResTarget.val.SubLink.location,\n });\n }\n }\n }\n\n // Detect unbounded queries (missing WHERE or LIMIT on table queries)\n if (is(node, \"SelectStmt\")) {\n // Only check top-level SELECT statements (not subqueries)\n const isSubquery = stack.some(\n (item) =>\n item === \"RangeSubselect\" ||\n item === \"SubLink\" ||\n item === \"CommonTableExpr\",\n );\n\n if (!isSubquery) {\n const hasFromClause =\n node.SelectStmt.fromClause && node.SelectStmt.fromClause.length > 0;\n if (hasFromClause) {\n // Check if this SELECT queries actual tables (not just subqueries or CTEs)\n const hasActualTables = node.SelectStmt.fromClause!.some((fromItem) => {\n return (\n is(fromItem, \"RangeVar\") ||\n (is(fromItem, \"JoinExpr\") && hasActualTablesInJoin(fromItem))\n );\n });\n\n if (hasActualTables) {\n const firstTable = node.SelectStmt.fromClause!.find((item) =>\n is(item, \"RangeVar\"),\n );\n const fromLocation = firstTable?.RangeVar.location;\n\n if (!node.SelectStmt.whereClause) {\n nudges.push({\n kind: \"MISSING_WHERE_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing WHERE clause\",\n location: fromLocation,\n });\n }\n\n if (!node.SelectStmt.limitCount) {\n nudges.push({\n kind: \"MISSING_LIMIT_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing LIMIT clause\",\n location: fromLocation,\n });\n }\n }\n }\n }\n }\n\n // Detect LIMIT without ORDER BY (non-deterministic results)\n // LIMIT ALL parses as limitCount with A_Const.isnull — exclude it\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.limitCount &&\n !isNullConstant(node.SelectStmt.limitCount) &&\n !node.SelectStmt.sortClause\n ) {\n nudges.push({\n kind: \"LIMIT_WITHOUT_ORDER_BY\",\n severity: \"WARNING\",\n message:\n \"LIMIT without ORDER BY returns non-deterministic results — add ORDER BY for predictable output\",\n });\n }\n\n // Detect ORDER BY DESC without explicit NULLS LAST (NULLs sort first by default)\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (!is(sortItem, \"SortBy\")) continue;\n\n const sortDir = sortItem.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\";\n const sortNulls = sortItem.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\";\n\n if (sortDir === \"SORTBY_DESC\" && sortNulls === \"SORTBY_NULLS_DEFAULT\") {\n if (sortItem.SortBy.node && is(sortItem.SortBy.node, \"ColumnRef\")) {\n const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);\n\n const hasIsNotNull =\n sortColumnName !== null &&\n whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName);\n\n if (!hasIsNotNull) {\n nudges.push({\n kind: \"NULLS_FIRST_IN_DESC_ORDER\",\n severity: \"INFO\",\n message:\n \"ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end\",\n location: sortItem.SortBy.node.ColumnRef.location,\n });\n }\n }\n }\n }\n }\n\n // Detect NULL comparison issues (= NULL instead of IS NULL)\n if (is(node, \"A_Expr\")) {\n const isEqualityOp =\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0 &&\n is(node.A_Expr.name[0], \"String\") &&\n (node.A_Expr.name[0].String.sval === \"=\" ||\n node.A_Expr.name[0].String.sval === \"!=\" ||\n node.A_Expr.name[0].String.sval === \"<>\");\n\n if (isEqualityOp) {\n const leftIsNull = isNullConstant(node.A_Expr.lexpr);\n const rightIsNull = isNullConstant(node.A_Expr.rexpr);\n\n if (leftIsNull || rightIsNull) {\n nudges.push({\n kind: \"USE_IS_NULL_NOT_EQUALS\",\n severity: \"WARNING\",\n message: \"Use IS NULL instead of = or != or <> for NULL comparisons\",\n location: node.A_Expr.location,\n });\n }\n }\n\n // Detect LIKE with leading wildcards\n const isLikeOp =\n node.A_Expr.kind === \"AEXPR_LIKE\" || node.A_Expr.kind === \"AEXPR_ILIKE\";\n\n if (isLikeOp && node.A_Expr.rexpr) {\n const patternString = getStringConstantValue(node.A_Expr.rexpr);\n if (patternString && patternString.startsWith(\"%\")) {\n let stringNode: A_Const | undefined;\n if (is(node.A_Expr.rexpr, \"A_Const\")) {\n stringNode = node.A_Expr.rexpr.A_Const;\n }\n nudges.push({\n kind: \"AVOID_LEADING_WILDCARD_LIKE\",\n severity: \"WARNING\",\n message:\n \"Leading wildcard in LIKE/ILIKE prevents index usage — consider a GIN trigram index (pg_trgm) or full-text search\",\n location: stringNode?.location,\n });\n }\n }\n }\n\n // Detect ORDER BY random()\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (\n is(sortItem, \"SortBy\") &&\n sortItem.SortBy.node &&\n is(sortItem.SortBy.node, \"FuncCall\") &&\n sortItem.SortBy.node.FuncCall.funcname?.some(\n (name) => is(name, \"String\") && name.String.sval === \"random\",\n )\n ) {\n nudges.push({\n kind: \"AVOID_ORDER_BY_RANDOM\",\n severity: \"WARNING\",\n message: \"Avoid using ORDER BY random()\",\n location: sortItem.SortBy.node.FuncCall.location,\n });\n }\n }\n }\n\n // Detect DISTINCT usage\n if (is(node, \"SelectStmt\") && node.SelectStmt.distinctClause) {\n nudges.push({\n kind: \"AVOID_DISTINCT_WITHOUT_REASON\",\n severity: \"WARNING\",\n message: \"Avoid using DISTINCT without a reason\",\n });\n }\n\n // Detect cartesian joins (missing JOIN conditions) and functions on columns in ON\n if (is(node, \"JoinExpr\")) {\n if (node.JoinExpr.quals) {\n nudges.push(...findFuncCallsOnColumns(node.JoinExpr.quals));\n } else if (!node.JoinExpr.usingClause) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n });\n }\n }\n\n // Detect multiple tables in FROM without explicit JOINs (old-style cartesian joins)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.fromClause &&\n node.SelectStmt.fromClause.length > 1\n ) {\n // Check if there are multiple RangeVar (tables) directly in FROM clause\n const tables = node.SelectStmt.fromClause.filter((item) =>\n is(item, \"RangeVar\"),\n );\n if (tables.length > 1) {\n // Collect table aliases/names for cross-table equality check\n const tableNames = new Set(\n tables.map(\n (t) => t.RangeVar.alias?.aliasname || t.RangeVar.relname || \"\",\n ),\n );\n\n // Don't fire if WHERE already has a cross-table equality (old-style implicit join)\n if (\n !whereHasCrossTableEquality(node.SelectStmt.whereClause, tableNames)\n ) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n location: tables[1].RangeVar.location,\n });\n }\n }\n }\n\n // Detect too many OR conditions on the same column\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"OR_EXPR\") {\n const orCount = countBoolOrConditions(node);\n if (orCount >= 3 && allOrBranchesReferenceSameColumn(node)) {\n nudges.push({\n kind: \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\",\n severity: \"WARNING\",\n message: \"Consider using IN instead of many ORs\",\n location: node.BoolExpr.location,\n });\n }\n }\n\n // Detect NOT IN (SELECT ...) — prefer NOT EXISTS\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"NOT_EXPR\") {\n const args = node.BoolExpr.args;\n if (args && args.length === 1) {\n const inner = args[0];\n if (\n isANode(inner) &&\n is(inner, \"SubLink\") &&\n inner.SubLink.subLinkType === \"ANY_SUBLINK\"\n ) {\n nudges.push({\n kind: \"PREFER_NOT_EXISTS_OVER_NOT_IN\",\n severity: \"WARNING\",\n message: \"Prefer NOT EXISTS over NOT IN (SELECT ...)\",\n location: inner.SubLink.location,\n });\n }\n }\n }\n\n // Detect COUNT(column) or COUNT(1) — suggest COUNT(*)\n if (is(node, \"FuncCall\")) {\n const funcName = node.FuncCall.funcname;\n const isCount =\n funcName &&\n funcName.length === 1 &&\n is(funcName[0], \"String\") &&\n funcName[0].String.sval === \"count\";\n\n if (\n isCount &&\n node.FuncCall.args &&\n !node.FuncCall.agg_star &&\n !node.FuncCall.agg_distinct\n ) {\n nudges.push({\n kind: \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\",\n severity: \"INFO\",\n message:\n \"Prefer COUNT(*) over COUNT(column) or COUNT(1) — COUNT(*) counts rows without checking for NULLs. If you need to count non-NULL values, COUNT(column) is correct.\",\n location: node.FuncCall.location,\n });\n }\n }\n\n // Detect non-aggregate conditions in HAVING clause\n if (is(node, \"SelectStmt\") && node.SelectStmt.havingClause) {\n if (!containsAggregate(node.SelectStmt.havingClause)) {\n const having = node.SelectStmt.havingClause;\n let location: number | undefined;\n if (is(having, \"A_Expr\")) location = having.A_Expr.location;\n else if (is(having, \"BoolExpr\")) location = having.BoolExpr.location;\n\n nudges.push({\n kind: \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\",\n severity: \"INFO\",\n message: \"Non-aggregate condition in HAVING should be in WHERE\",\n location,\n });\n }\n }\n\n // Detect JSONB set-returning functions in WHERE context\n if (is(node, \"FuncCall\")) {\n const inWhereClause = stack.some((item) => item === \"whereClause\");\n if (inWhereClause && node.FuncCall.args) {\n const name = getFuncName(node);\n if (\n name &&\n JSONB_SET_RETURNING_FUNCTIONS.has(name) &&\n containsColumnRef(node.FuncCall.args)\n ) {\n nudges.push({\n kind: \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\",\n severity: \"INFO\",\n message:\n \"JSONB set-returning functions (e.g. jsonb_array_elements) cannot be used as an index access path. If the query checks for containment or key existence, GIN-compatible operators (@>, ?, ?|, ?&, @?, @@) may allow index usage.\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n\n // Too many parameters in a tuple\n if (is(node, \"A_Expr\")) {\n if (node.A_Expr.kind === \"AEXPR_IN\") {\n let list: List | undefined;\n if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"List\")) {\n list = node.A_Expr.lexpr.List;\n } else if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"List\")) {\n list = node.A_Expr.rexpr.List;\n }\n\n if (list?.items && list.items.length >= 10) {\n nudges.push({\n kind: \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\",\n message:\n \"`in (...)` queries with large tuples can often be replaced with `= ANY($1)` using a single parameter\",\n severity: \"INFO\",\n location: node.A_Expr.location,\n });\n }\n }\n }\n\n // Detect CASE inside aggregate functions (prefer FILTER)\n if (is(node, \"FuncCall\")) {\n const funcname =\n node.FuncCall.funcname?.[0] &&\n is(node.FuncCall.funcname[0], \"String\") &&\n node.FuncCall.funcname[0].String.sval;\n\n if (\n funcname &&\n [\"sum\", \"count\", \"avg\", \"min\", \"max\"].includes(funcname.toLowerCase())\n ) {\n const firstArg = node.FuncCall.args?.[0];\n if (firstArg && isANode(firstArg) && is(firstArg, \"CaseExpr\")) {\n const caseExpr = firstArg.CaseExpr;\n // Only flag simple CASE expressions (single WHEN clause)\n if (caseExpr.args && caseExpr.args.length === 1) {\n // Check ELSE clause: must be absent, ELSE 0, or ELSE NULL\n const defresult = caseExpr.defresult;\n const isSimpleElse =\n !defresult ||\n (isANode(defresult) &&\n is(defresult, \"A_Const\") &&\n (defresult.A_Const.isnull !== undefined ||\n (defresult.A_Const.ival !== undefined &&\n (defresult.A_Const.ival.ival === 0 ||\n defresult.A_Const.ival.ival === undefined))));\n\n if (isSimpleElse) {\n nudges.push({\n kind: \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\",\n severity: \"INFO\",\n message:\n \"Use FILTER (WHERE ...) instead of CASE inside aggregate functions\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n }\n }\n\n // Detect UNION without ALL (implicit deduplication)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.op === \"SETOP_UNION\" &&\n !node.SelectStmt.all\n ) {\n nudges.push({\n kind: \"PREFER_UNION_ALL_OVER_UNION\",\n severity: \"INFO\",\n message:\n \"UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed\",\n });\n }\n\n // Detect COUNT(*)/COUNT(1) compared to 0 or 1 (existence check)\n if (\n is(node, \"A_Expr\") &&\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0\n ) {\n const opNode = node.A_Expr.name[0];\n const op = is(opNode, \"String\") ? opNode.String.sval : null;\n\n if (\n op &&\n isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)\n ) {\n nudges.push({\n kind: \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\",\n severity: \"INFO\",\n message: \"Use EXISTS instead of COUNT for existence checks\",\n location: node.A_Expr.location,\n });\n }\n }\n\n return nudges;\n}\n\nfunction containsColumnRef(args: unknown[]): boolean {\n // Recursively check if any argument contains a ColumnRef\n for (const arg of args) {\n if (hasColumnRefInNode(arg)) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasColumnRefInNode(node: unknown): boolean {\n if (isANode(node) && is(node, \"ColumnRef\")) {\n return true;\n }\n\n if (typeof node !== \"object\" || node === null) {\n return false;\n }\n\n if (Array.isArray(node)) {\n return node.some((item) => hasColumnRefInNode(item));\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return hasColumnRefInNode(node[keys[0]]);\n }\n\n for (const child of Object.values(node)) {\n if (hasColumnRefInNode(child)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasActualTablesInJoin(\n joinExpr: Extract<Node, { JoinExpr: unknown }>,\n): boolean {\n // Check left side of join\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.larg)) {\n return true;\n }\n }\n\n // Check right side of join\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.rarg)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction isNullConstant(node: unknown): boolean {\n if (!node || typeof node !== \"object\") {\n return false;\n }\n\n if (isANode(node) && is(node, \"A_Const\")) {\n // Check if it's a NULL constant\n return node.A_Const.isnull !== undefined;\n }\n\n return false;\n}\n\nfunction getStringConstantValue(node: unknown): string | null {\n if (!node || typeof node !== \"object\") {\n return null;\n }\n\n if (isANode(node) && is(node, \"A_Const\") && node.A_Const.sval) {\n return node.A_Const.sval.sval || null;\n }\n\n return null;\n}\n\nconst JSONB_SET_RETURNING_FUNCTIONS = new Set([\n \"jsonb_array_elements\",\n \"json_array_elements\",\n \"jsonb_array_elements_text\",\n \"json_array_elements_text\",\n \"jsonb_each\",\n \"json_each\",\n \"jsonb_each_text\",\n \"json_each_text\",\n]);\n\nfunction getFuncName(\n node: Extract<Node, { FuncCall: unknown }>,\n): string | null {\n const names = node.FuncCall.funcname;\n if (!names || names.length === 0) return null;\n const last = names[names.length - 1];\n if (isANode(last) && is(last, \"String\") && last.String.sval) {\n return last.String.sval;\n }\n return null;\n}\n\nfunction getLastColumnRefField(\n columnRef: Extract<Node, { ColumnRef: unknown }>,\n): string | null {\n const fields = columnRef.ColumnRef.fields;\n if (!fields || fields.length === 0) return null;\n\n const lastField = fields[fields.length - 1];\n if (isANode(lastField) && is(lastField, \"String\")) {\n return lastField.String.sval || null;\n }\n return null;\n}\n\nfunction whereHasIsNotNull(\n whereClause: Node | undefined,\n columnName: string,\n): boolean {\n if (!whereClause) return false;\n\n let found = false;\n Walker.shallowMatch(whereClause, \"NullTest\", (node) => {\n if (\n node.NullTest.nulltesttype === \"IS_NOT_NULL\" &&\n node.NullTest.arg &&\n is(node.NullTest.arg, \"ColumnRef\")\n ) {\n const name = getLastColumnRefField(node.NullTest.arg);\n if (name === columnName) {\n found = true;\n }\n }\n });\n\n return found;\n}\n\nconst AGGREGATE_FUNCTIONS = new Set([\n \"count\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"array_agg\",\n \"string_agg\",\n \"bool_and\",\n \"bool_or\",\n \"every\",\n]);\n\nfunction containsAggregate(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n\n if (Array.isArray(node)) {\n return node.some(containsAggregate);\n }\n\n if (isANode(node) && is(node, \"FuncCall\")) {\n const funcname = node.FuncCall.funcname;\n if (funcname) {\n for (const f of funcname) {\n if (\n isANode(f) &&\n is(f, \"String\") &&\n AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? \"\")\n ) {\n return true;\n }\n }\n }\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return containsAggregate(node[keys[0]]);\n }\n\n for (const child of Object.values(node)) {\n if (containsAggregate(child)) return true;\n }\n\n return false;\n}\n\nfunction countBoolOrConditions(\n node: Extract<Node, { BoolExpr: unknown }>,\n): number {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return 1;\n }\n\n let count = 0;\n for (const arg of node.BoolExpr.args) {\n if (\n isANode(arg) &&\n is(arg, \"BoolExpr\") &&\n arg.BoolExpr.boolop === \"OR_EXPR\"\n ) {\n count += countBoolOrConditions(arg);\n } else {\n count += 1;\n }\n }\n\n return count;\n}\n\n/**\n * Check whether every leaf of a top-level OR expression references the same\n * left-hand column (e.g. `status = 'a' OR status = 'b' OR status = 'c'`).\n * Returns false when ORs span different columns — IN rewrite doesn't apply.\n */\nfunction allOrBranchesReferenceSameColumn(\n node: Extract<Node, { BoolExpr: unknown }>,\n): boolean {\n const columns = collectOrLeafColumns(node);\n if (columns.length === 0) return false;\n return columns.every((col) => col === columns[0]);\n}\n\nfunction collectOrLeafColumns(\n node: Extract<Node, { BoolExpr: unknown }>,\n): string[] {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return [];\n }\n\n const columns: string[] = [];\n for (const arg of node.BoolExpr.args) {\n if (!isANode(arg)) continue;\n if (is(arg, \"BoolExpr\") && arg.BoolExpr.boolop === \"OR_EXPR\") {\n columns.push(...collectOrLeafColumns(arg));\n } else if (is(arg, \"A_Expr\")) {\n const col = getLeftColumnKey(arg);\n if (col) {\n columns.push(col);\n } else {\n // Non-column comparison — can't be rewritten as IN\n return [];\n }\n } else {\n // Non-A_Expr leaf (e.g. SubLink, BoolExpr AND) — not a simple OR chain\n return [];\n }\n }\n return columns;\n}\n\n/**\n * Get a string key for the left-hand column of an A_Expr equality comparison.\n * For `t1.col = value` returns `\"t1.col\"`, for `col = value` returns `\"col\"`.\n */\nfunction getLeftColumnKey(\n expr: Extract<Node, { A_Expr: unknown }>,\n): string | null {\n if (!expr.A_Expr.lexpr || !isANode(expr.A_Expr.lexpr)) return null;\n if (!is(expr.A_Expr.lexpr, \"ColumnRef\")) return null;\n const fields = expr.A_Expr.lexpr.ColumnRef.fields;\n if (!fields) return null;\n return fields\n .filter(\n (f): f is Extract<Node, { String: unknown }> =>\n isANode(f) && is(f, \"String\"),\n )\n .map((f) => f.String.sval)\n .join(\".\");\n}\n\n/**\n * Check if a WHERE clause contains an equality between columns from different\n * tables (indicating an old-style implicit join condition).\n */\nfunction whereHasCrossTableEquality(\n whereClause: Node | undefined,\n tableNames: Set<string>,\n): boolean {\n if (!whereClause) return false;\n\n if (isANode(whereClause) && is(whereClause, \"A_Expr\")) {\n if (\n whereClause.A_Expr.kind === \"AEXPR_OP\" &&\n whereClause.A_Expr.name?.some(\n (n) => is(n, \"String\") && n.String.sval === \"=\",\n )\n ) {\n const leftTable = getColumnRefTableQualifier(whereClause.A_Expr.lexpr);\n const rightTable = getColumnRefTableQualifier(whereClause.A_Expr.rexpr);\n if (\n leftTable &&\n rightTable &&\n leftTable !== rightTable &&\n tableNames.has(leftTable) &&\n tableNames.has(rightTable)\n ) {\n return true;\n }\n }\n }\n\n // Recurse into AND/OR boolean expressions\n if (isANode(whereClause) && is(whereClause, \"BoolExpr\")) {\n for (const arg of whereClause.BoolExpr.args ?? []) {\n if (isANode(arg) && whereHasCrossTableEquality(arg, tableNames)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Extract the table qualifier (first field) from a ColumnRef node.\n * e.g. `t1.uid` → `\"t1\"`, `uid` → null\n */\nfunction getColumnRefTableQualifier(node: unknown): string | null {\n if (!node || !isANode(node as Node) || !is(node as Node, \"ColumnRef\"))\n return null;\n const fields = (node as Extract<Node, { ColumnRef: unknown }>).ColumnRef\n .fields;\n if (!fields || fields.length < 2) return null;\n const first = fields[0];\n if (isANode(first) && is(first, \"String\")) {\n return first.String.sval || null;\n }\n return null;\n}\n\nfunction isCountFuncCall(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"FuncCall\")) return false;\n const fc = node.FuncCall;\n const isCount =\n fc.funcname?.some((n) => is(n, \"String\") && n.String.sval === \"count\") ??\n false;\n if (!isCount) return false;\n // COUNT(*) has agg_star\n if (fc.agg_star) return true;\n // COUNT(1) or COUNT(literal) — single constant argument\n if (\n fc.args &&\n fc.args.length === 1 &&\n isANode(fc.args[0]) &&\n is(fc.args[0], \"A_Const\")\n ) {\n return true;\n }\n return false;\n}\n\nfunction isSubLinkWithCount(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"SubLink\")) return false;\n const sub = node.SubLink;\n const subselect = sub.subselect;\n if (!subselect || !isANode(subselect) || !is(subselect, \"SelectStmt\"))\n return false;\n const targets = subselect.SelectStmt.targetList;\n if (!targets || targets.length !== 1) return false;\n const target = targets[0];\n if (!isANode(target) || !is(target, \"ResTarget\") || !target.ResTarget.val)\n return false;\n return isCountFuncCall(target.ResTarget.val);\n}\n\nfunction isCountExpression(node: unknown): boolean {\n return isCountFuncCall(node) || isSubLinkWithCount(node);\n}\n\nfunction getIntegerConstantValue(node: unknown): number | null {\n if (!node || typeof node !== \"object\") return null;\n if (!isANode(node) || !is(node, \"A_Const\")) return null;\n if (node.A_Const.ival === undefined) return null;\n // protobuf: ival: {} means 0, ival: { ival: N } means N\n return node.A_Const.ival.ival ?? 0;\n}\n\nfunction isExistenceCheckPattern(\n lexpr: unknown,\n rexpr: unknown,\n op: string,\n): boolean {\n // count_expr > 0, count_expr >= 1, count_expr != 0, count_expr <> 0\n if (isCountExpression(lexpr)) {\n const val = getIntegerConstantValue(rexpr);\n if (val !== null) {\n if (op === \">\" && val === 0) return true;\n if (op === \">=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n // Reversed: 0 < count_expr, 1 <= count_expr, 0 != count_expr\n if (isCountExpression(rexpr)) {\n const val = getIntegerConstantValue(lexpr);\n if (val !== null) {\n if (op === \"<\" && val === 0) return true;\n if (op === \"<=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nudges.mjs","names":[],"sources":["../../src/sql/nudges.ts"],"sourcesContent":["import type { A_Const, List, Node, ResTarget } from \"@pgsql/types\";\nimport { is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\ntype NudgeKind =\n | \"LARGE_IMPROVEMENT_FOUND\"\n | \"SMALL_IMPROVEMENT_FOUND\"\n | \"AVOID_SELECT_STAR\"\n | \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\"\n | \"MISSING_WHERE_CLAUSE\"\n | \"MISSING_LIMIT_CLAUSE\"\n | \"USE_IS_NULL_NOT_EQUALS\"\n | \"AVOID_DISTINCT_WITHOUT_REASON\"\n | \"MISSING_JOIN_CONDITION\"\n | \"AVOID_LEADING_WILDCARD_LIKE\"\n | \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\"\n | \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\"\n | \"PREFER_NOT_EXISTS_OVER_NOT_IN\"\n | \"AVOID_ORDER_BY_RANDOM\"\n | \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\"\n | \"PREFER_UNION_ALL_OVER_UNION\"\n | \"NULLS_FIRST_IN_DESC_ORDER\"\n | \"AVOID_SCALAR_SUBQUERY_IN_SELECT\"\n | \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\"\n | \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\"\n | \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\"\n | \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\"\n | \"LIMIT_WITHOUT_ORDER_BY\";\n\nexport type Nudge = {\n kind: NudgeKind;\n severity: \"CRITICAL\" | \"WARNING\" | \"INFO\";\n message: string;\n location?: number;\n};\n\ntype NudgeCreator = (node: Node) => Nudge[];\n\nconst findFuncCallsOnColumns: NudgeCreator = (whereClause) => {\n const nudges: Nudge[] = [];\n Walker.shallowMatch(whereClause, \"FuncCall\", (node) => {\n if (node.FuncCall.args && containsColumnRef(node.FuncCall.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.FuncCall.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"CoalesceExpr\", (node) => {\n if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message:\n \"Avoid using functions on columns in conditions — prevents index usage\",\n location: node.CoalesceExpr.location,\n });\n }\n });\n Walker.shallowMatch(whereClause, \"TypeCast\", (node) => {\n if (node.TypeCast.arg && hasColumnRefInNode(node.TypeCast.arg)) {\n nudges.push({\n kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n severity: \"WARNING\",\n message: \"Avoid using functions on columns in WHERE clause\",\n location: node.TypeCast.location,\n });\n }\n });\n return nudges;\n};\n\n/**\n * Detect nudges for a single node during AST traversal.\n * Returns an array of nudges found for this node.\n */\nexport function parseNudges(\n node: Node,\n stack: (KeysOfUnion<Node> | string)[],\n): Nudge[] {\n const nudges: Nudge[] = [];\n\n if (is(node, \"SelectStmt\")) {\n if (node.SelectStmt.whereClause) {\n nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));\n }\n const star = node.SelectStmt.targetList?.find(\n (target): target is { ResTarget: ResTarget } => {\n if (\n !(\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"ColumnRef\")\n )\n ) {\n return false;\n }\n const fields = target.ResTarget.val.ColumnRef.fields;\n if (!fields?.some((field) => is(field, \"A_Star\"))) {\n return false;\n }\n // Qualified star (e.g. users.*) is deliberate — skip\n if (fields.length > 1) {\n return false;\n }\n return true;\n },\n );\n if (star) {\n // Skip when all FROM items are subqueries — the outer * just\n // passes through whatever the subquery already selected.\n const fromClause = node.SelectStmt.fromClause;\n const allSubselects =\n fromClause &&\n fromClause.length > 0 &&\n fromClause.every((item) => is(item, \"RangeSubselect\"));\n\n if (!allSubselects) {\n nudges.push({\n kind: \"AVOID_SELECT_STAR\",\n severity: \"INFO\",\n message: \"Avoid using SELECT *\",\n location: star.ResTarget.location,\n });\n }\n }\n\n // Detect correlated scalar subqueries in SELECT (N+1 problem)\n for (const target of node.SelectStmt.targetList ?? []) {\n if (\n is(target, \"ResTarget\") &&\n target.ResTarget.val &&\n is(target.ResTarget.val, \"SubLink\") &&\n target.ResTarget.val.SubLink.subLinkType === \"EXPR_SUBLINK\"\n ) {\n nudges.push({\n kind: \"AVOID_SCALAR_SUBQUERY_IN_SELECT\",\n severity: \"WARNING\",\n message:\n \"Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN\",\n location: target.ResTarget.val.SubLink.location,\n });\n }\n }\n }\n\n // Detect unbounded queries (missing WHERE or LIMIT on table queries)\n if (is(node, \"SelectStmt\")) {\n // Only check top-level SELECT statements (not subqueries)\n const isSubquery = stack.some(\n (item) =>\n item === \"RangeSubselect\" ||\n item === \"SubLink\" ||\n item === \"CommonTableExpr\",\n );\n\n if (!isSubquery) {\n const hasFromClause =\n node.SelectStmt.fromClause && node.SelectStmt.fromClause.length > 0;\n if (hasFromClause) {\n // Check if this SELECT queries actual tables (not just subqueries or CTEs)\n const hasActualTables = node.SelectStmt.fromClause!.some((fromItem) => {\n return (\n is(fromItem, \"RangeVar\") ||\n (is(fromItem, \"JoinExpr\") && hasActualTablesInJoin(fromItem))\n );\n });\n\n if (hasActualTables) {\n const firstTable = node.SelectStmt.fromClause!.find((item) =>\n is(item, \"RangeVar\"),\n );\n const fromLocation = firstTable?.RangeVar.location;\n\n if (!node.SelectStmt.whereClause) {\n nudges.push({\n kind: \"MISSING_WHERE_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing WHERE clause\",\n location: fromLocation,\n });\n }\n\n // A bare aggregate select (aggregates in the target list, no GROUP BY)\n // returns exactly one row, so a LIMIT adds nothing. Aggregates with\n // GROUP BY can still return many rows and stay eligible.\n const isBareAggregate =\n !node.SelectStmt.groupClause?.length &&\n containsAggregate(node.SelectStmt.targetList, true);\n\n if (!node.SelectStmt.limitCount && !isBareAggregate) {\n nudges.push({\n kind: \"MISSING_LIMIT_CLAUSE\",\n severity: \"INFO\",\n message: \"Missing LIMIT clause\",\n location: fromLocation,\n });\n }\n }\n }\n }\n }\n\n // Detect LIMIT without ORDER BY (non-deterministic results)\n // LIMIT ALL parses as limitCount with A_Const.isnull — exclude it\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.limitCount &&\n !isNullConstant(node.SelectStmt.limitCount) &&\n !node.SelectStmt.sortClause\n ) {\n nudges.push({\n kind: \"LIMIT_WITHOUT_ORDER_BY\",\n severity: \"WARNING\",\n message:\n \"LIMIT without ORDER BY returns non-deterministic results — add ORDER BY for predictable output\",\n });\n }\n\n // Detect ORDER BY DESC without explicit NULLS LAST (NULLs sort first by default)\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (!is(sortItem, \"SortBy\")) continue;\n\n const sortDir = sortItem.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\";\n const sortNulls = sortItem.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\";\n\n if (sortDir === \"SORTBY_DESC\" && sortNulls === \"SORTBY_NULLS_DEFAULT\") {\n if (sortItem.SortBy.node && is(sortItem.SortBy.node, \"ColumnRef\")) {\n const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);\n\n const hasIsNotNull =\n sortColumnName !== null &&\n whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName);\n\n if (!hasIsNotNull) {\n nudges.push({\n kind: \"NULLS_FIRST_IN_DESC_ORDER\",\n severity: \"INFO\",\n message:\n \"ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end\",\n location: sortItem.SortBy.node.ColumnRef.location,\n });\n }\n }\n }\n }\n }\n\n // Detect NULL comparison issues (= NULL instead of IS NULL)\n if (is(node, \"A_Expr\")) {\n const isEqualityOp =\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0 &&\n is(node.A_Expr.name[0], \"String\") &&\n (node.A_Expr.name[0].String.sval === \"=\" ||\n node.A_Expr.name[0].String.sval === \"!=\" ||\n node.A_Expr.name[0].String.sval === \"<>\");\n\n if (isEqualityOp) {\n const leftIsNull = isNullConstant(node.A_Expr.lexpr);\n const rightIsNull = isNullConstant(node.A_Expr.rexpr);\n\n if (leftIsNull || rightIsNull) {\n nudges.push({\n kind: \"USE_IS_NULL_NOT_EQUALS\",\n severity: \"WARNING\",\n message: \"Use IS NULL instead of = or != or <> for NULL comparisons\",\n location: node.A_Expr.location,\n });\n }\n }\n\n // Detect LIKE with leading wildcards\n const isLikeOp =\n node.A_Expr.kind === \"AEXPR_LIKE\" || node.A_Expr.kind === \"AEXPR_ILIKE\";\n\n if (isLikeOp && node.A_Expr.rexpr) {\n const patternString = getStringConstantValue(node.A_Expr.rexpr);\n if (patternString && patternString.startsWith(\"%\")) {\n let stringNode: A_Const | undefined;\n if (is(node.A_Expr.rexpr, \"A_Const\")) {\n stringNode = node.A_Expr.rexpr.A_Const;\n }\n nudges.push({\n kind: \"AVOID_LEADING_WILDCARD_LIKE\",\n severity: \"WARNING\",\n message:\n \"Leading wildcard in LIKE/ILIKE prevents index usage — consider a GIN trigram index (pg_trgm) or full-text search\",\n location: stringNode?.location,\n });\n }\n }\n }\n\n // Detect ORDER BY random()\n if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n for (const sortItem of node.SelectStmt.sortClause) {\n if (\n is(sortItem, \"SortBy\") &&\n sortItem.SortBy.node &&\n is(sortItem.SortBy.node, \"FuncCall\") &&\n sortItem.SortBy.node.FuncCall.funcname?.some(\n (name) => is(name, \"String\") && name.String.sval === \"random\",\n )\n ) {\n nudges.push({\n kind: \"AVOID_ORDER_BY_RANDOM\",\n severity: \"WARNING\",\n message: \"Avoid using ORDER BY random()\",\n location: sortItem.SortBy.node.FuncCall.location,\n });\n }\n }\n }\n\n // Detect DISTINCT usage\n if (is(node, \"SelectStmt\") && node.SelectStmt.distinctClause) {\n nudges.push({\n kind: \"AVOID_DISTINCT_WITHOUT_REASON\",\n severity: \"WARNING\",\n message: \"Avoid using DISTINCT without a reason\",\n });\n }\n\n // Detect cartesian joins (missing JOIN conditions) and functions on columns in ON\n if (is(node, \"JoinExpr\")) {\n if (node.JoinExpr.quals) {\n nudges.push(...findFuncCallsOnColumns(node.JoinExpr.quals));\n } else if (!node.JoinExpr.usingClause) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n });\n }\n }\n\n // Detect multiple tables in FROM without explicit JOINs (old-style cartesian joins)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.fromClause &&\n node.SelectStmt.fromClause.length > 1\n ) {\n // Check if there are multiple RangeVar (tables) directly in FROM clause\n const tables = node.SelectStmt.fromClause.filter((item) =>\n is(item, \"RangeVar\"),\n );\n if (tables.length > 1) {\n // Collect table aliases/names for cross-table equality check\n const tableNames = new Set(\n tables.map(\n (t) => t.RangeVar.alias?.aliasname || t.RangeVar.relname || \"\",\n ),\n );\n\n // Don't fire if WHERE already has a cross-table equality (old-style implicit join)\n if (\n !whereHasCrossTableEquality(node.SelectStmt.whereClause, tableNames)\n ) {\n nudges.push({\n kind: \"MISSING_JOIN_CONDITION\",\n severity: \"WARNING\",\n message: \"Missing JOIN condition\",\n location: tables[1].RangeVar.location,\n });\n }\n }\n }\n\n // Detect too many OR conditions on the same column\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"OR_EXPR\") {\n const orCount = countBoolOrConditions(node);\n if (orCount >= 3 && allOrBranchesReferenceSameColumn(node)) {\n nudges.push({\n kind: \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\",\n severity: \"WARNING\",\n message: \"Consider using IN instead of many ORs\",\n location: node.BoolExpr.location,\n });\n }\n }\n\n // Detect NOT IN (SELECT ...) — prefer NOT EXISTS\n if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"NOT_EXPR\") {\n const args = node.BoolExpr.args;\n if (args && args.length === 1) {\n const inner = args[0];\n if (\n isANode(inner) &&\n is(inner, \"SubLink\") &&\n inner.SubLink.subLinkType === \"ANY_SUBLINK\"\n ) {\n nudges.push({\n kind: \"PREFER_NOT_EXISTS_OVER_NOT_IN\",\n severity: \"WARNING\",\n message: \"Prefer NOT EXISTS over NOT IN (SELECT ...)\",\n location: inner.SubLink.location,\n });\n }\n }\n }\n\n // Detect COUNT(column) or COUNT(1) — suggest COUNT(*)\n if (is(node, \"FuncCall\")) {\n const funcName = node.FuncCall.funcname;\n const isCount =\n funcName &&\n funcName.length === 1 &&\n is(funcName[0], \"String\") &&\n funcName[0].String.sval === \"count\";\n\n if (\n isCount &&\n node.FuncCall.args &&\n !node.FuncCall.agg_star &&\n !node.FuncCall.agg_distinct\n ) {\n nudges.push({\n kind: \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\",\n severity: \"INFO\",\n message:\n \"Prefer COUNT(*) over COUNT(column) or COUNT(1) — COUNT(*) counts rows without checking for NULLs. If you need to count non-NULL values, COUNT(column) is correct.\",\n location: node.FuncCall.location,\n });\n }\n }\n\n // Detect non-aggregate conditions in HAVING clause\n if (is(node, \"SelectStmt\") && node.SelectStmt.havingClause) {\n if (!containsAggregate(node.SelectStmt.havingClause)) {\n const having = node.SelectStmt.havingClause;\n let location: number | undefined;\n if (is(having, \"A_Expr\")) location = having.A_Expr.location;\n else if (is(having, \"BoolExpr\")) location = having.BoolExpr.location;\n\n nudges.push({\n kind: \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\",\n severity: \"INFO\",\n message: \"Non-aggregate condition in HAVING should be in WHERE\",\n location,\n });\n }\n }\n\n // Detect JSONB set-returning functions in WHERE context\n if (is(node, \"FuncCall\")) {\n const inWhereClause = stack.some((item) => item === \"whereClause\");\n if (inWhereClause && node.FuncCall.args) {\n const name = getFuncName(node);\n if (\n name &&\n JSONB_SET_RETURNING_FUNCTIONS.has(name) &&\n containsColumnRef(node.FuncCall.args)\n ) {\n nudges.push({\n kind: \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\",\n severity: \"INFO\",\n message:\n \"JSONB set-returning functions (e.g. jsonb_array_elements) cannot be used as an index access path. If the query checks for containment or key existence, GIN-compatible operators (@>, ?, ?|, ?&, @?, @@) may allow index usage.\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n\n // Too many parameters in a tuple\n if (is(node, \"A_Expr\")) {\n if (node.A_Expr.kind === \"AEXPR_IN\") {\n let list: List | undefined;\n if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"List\")) {\n list = node.A_Expr.lexpr.List;\n } else if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"List\")) {\n list = node.A_Expr.rexpr.List;\n }\n\n if (list?.items && list.items.length >= 10) {\n nudges.push({\n kind: \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\",\n message:\n \"`in (...)` queries with large tuples can often be replaced with `= ANY($1)` using a single parameter\",\n severity: \"INFO\",\n location: node.A_Expr.location,\n });\n }\n }\n }\n\n // Detect CASE inside aggregate functions (prefer FILTER)\n if (is(node, \"FuncCall\")) {\n const funcname =\n node.FuncCall.funcname?.[0] &&\n is(node.FuncCall.funcname[0], \"String\") &&\n node.FuncCall.funcname[0].String.sval;\n\n if (\n funcname &&\n [\"sum\", \"count\", \"avg\", \"min\", \"max\"].includes(funcname.toLowerCase())\n ) {\n const firstArg = node.FuncCall.args?.[0];\n if (firstArg && isANode(firstArg) && is(firstArg, \"CaseExpr\")) {\n const caseExpr = firstArg.CaseExpr;\n // Only flag simple CASE expressions (single WHEN clause)\n if (caseExpr.args && caseExpr.args.length === 1) {\n // Check ELSE clause: must be absent, ELSE 0, or ELSE NULL\n const defresult = caseExpr.defresult;\n const isSimpleElse =\n !defresult ||\n (isANode(defresult) &&\n is(defresult, \"A_Const\") &&\n (defresult.A_Const.isnull !== undefined ||\n (defresult.A_Const.ival !== undefined &&\n (defresult.A_Const.ival.ival === 0 ||\n defresult.A_Const.ival.ival === undefined))));\n\n if (isSimpleElse) {\n nudges.push({\n kind: \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\",\n severity: \"INFO\",\n message:\n \"Use FILTER (WHERE ...) instead of CASE inside aggregate functions\",\n location: node.FuncCall.location,\n });\n }\n }\n }\n }\n }\n\n // Detect UNION without ALL (implicit deduplication)\n if (\n is(node, \"SelectStmt\") &&\n node.SelectStmt.op === \"SETOP_UNION\" &&\n !node.SelectStmt.all\n ) {\n nudges.push({\n kind: \"PREFER_UNION_ALL_OVER_UNION\",\n severity: \"INFO\",\n message:\n \"UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed\",\n });\n }\n\n // Detect COUNT(*)/COUNT(1) compared to 0 or 1 (existence check)\n if (\n is(node, \"A_Expr\") &&\n node.A_Expr.kind === \"AEXPR_OP\" &&\n node.A_Expr.name &&\n node.A_Expr.name.length > 0\n ) {\n const opNode = node.A_Expr.name[0];\n const op = is(opNode, \"String\") ? opNode.String.sval : null;\n\n if (\n op &&\n isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)\n ) {\n nudges.push({\n kind: \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\",\n severity: \"INFO\",\n message: \"Use EXISTS instead of COUNT for existence checks\",\n location: node.A_Expr.location,\n });\n }\n }\n\n return nudges;\n}\n\nfunction containsColumnRef(args: unknown[]): boolean {\n // Recursively check if any argument contains a ColumnRef\n for (const arg of args) {\n if (hasColumnRefInNode(arg)) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasColumnRefInNode(node: unknown): boolean {\n if (isANode(node) && is(node, \"ColumnRef\")) {\n return true;\n }\n\n if (typeof node !== \"object\" || node === null) {\n return false;\n }\n\n if (Array.isArray(node)) {\n return node.some((item) => hasColumnRefInNode(item));\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return hasColumnRefInNode(node[keys[0]]);\n }\n\n for (const child of Object.values(node)) {\n if (hasColumnRefInNode(child)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasActualTablesInJoin(\n joinExpr: Extract<Node, { JoinExpr: unknown }>,\n): boolean {\n // Check left side of join\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.larg)) {\n return true;\n }\n }\n\n // Check right side of join\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"RangeVar\")) {\n return true;\n }\n if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"JoinExpr\")) {\n if (hasActualTablesInJoin(joinExpr.JoinExpr.rarg)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction isNullConstant(node: unknown): boolean {\n if (!node || typeof node !== \"object\") {\n return false;\n }\n\n if (isANode(node) && is(node, \"A_Const\")) {\n // Check if it's a NULL constant\n return node.A_Const.isnull !== undefined;\n }\n\n return false;\n}\n\nfunction getStringConstantValue(node: unknown): string | null {\n if (!node || typeof node !== \"object\") {\n return null;\n }\n\n if (isANode(node) && is(node, \"A_Const\") && node.A_Const.sval) {\n return node.A_Const.sval.sval || null;\n }\n\n return null;\n}\n\nconst JSONB_SET_RETURNING_FUNCTIONS = new Set([\n \"jsonb_array_elements\",\n \"json_array_elements\",\n \"jsonb_array_elements_text\",\n \"json_array_elements_text\",\n \"jsonb_each\",\n \"json_each\",\n \"jsonb_each_text\",\n \"json_each_text\",\n]);\n\nfunction getFuncName(\n node: Extract<Node, { FuncCall: unknown }>,\n): string | null {\n const names = node.FuncCall.funcname;\n if (!names || names.length === 0) return null;\n const last = names[names.length - 1];\n if (isANode(last) && is(last, \"String\") && last.String.sval) {\n return last.String.sval;\n }\n return null;\n}\n\nfunction getLastColumnRefField(\n columnRef: Extract<Node, { ColumnRef: unknown }>,\n): string | null {\n const fields = columnRef.ColumnRef.fields;\n if (!fields || fields.length === 0) return null;\n\n const lastField = fields[fields.length - 1];\n if (isANode(lastField) && is(lastField, \"String\")) {\n return lastField.String.sval || null;\n }\n return null;\n}\n\nfunction whereHasIsNotNull(\n whereClause: Node | undefined,\n columnName: string,\n): boolean {\n if (!whereClause) return false;\n\n let found = false;\n Walker.shallowMatch(whereClause, \"NullTest\", (node) => {\n if (\n node.NullTest.nulltesttype === \"IS_NOT_NULL\" &&\n node.NullTest.arg &&\n is(node.NullTest.arg, \"ColumnRef\")\n ) {\n const name = getLastColumnRefField(node.NullTest.arg);\n if (name === columnName) {\n found = true;\n }\n }\n });\n\n return found;\n}\n\nconst AGGREGATE_FUNCTIONS = new Set([\n \"count\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"array_agg\",\n \"string_agg\",\n \"bool_and\",\n \"bool_or\",\n \"every\",\n]);\n\nfunction containsAggregate(node: unknown, skipSubqueries = false): boolean {\n if (!node || typeof node !== \"object\") return false;\n\n if (Array.isArray(node)) {\n return node.some((child) => containsAggregate(child, skipSubqueries));\n }\n\n // A subquery's aggregate doesn't bound the enclosing query, so callers that\n // reason about the enclosing statement (e.g. bare-aggregate detection) skip it.\n if (skipSubqueries && isANode(node) && is(node, \"SubLink\")) {\n return false;\n }\n\n if (isANode(node) && is(node, \"FuncCall\")) {\n const funcname = node.FuncCall.funcname;\n if (funcname) {\n for (const f of funcname) {\n if (\n isANode(f) &&\n is(f, \"String\") &&\n AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? \"\")\n ) {\n return true;\n }\n }\n }\n }\n\n if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n return containsAggregate(node[keys[0]], skipSubqueries);\n }\n\n for (const child of Object.values(node)) {\n if (containsAggregate(child, skipSubqueries)) return true;\n }\n\n return false;\n}\n\nfunction countBoolOrConditions(\n node: Extract<Node, { BoolExpr: unknown }>,\n): number {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return 1;\n }\n\n let count = 0;\n for (const arg of node.BoolExpr.args) {\n if (\n isANode(arg) &&\n is(arg, \"BoolExpr\") &&\n arg.BoolExpr.boolop === \"OR_EXPR\"\n ) {\n count += countBoolOrConditions(arg);\n } else {\n count += 1;\n }\n }\n\n return count;\n}\n\n/**\n * Check whether every leaf of a top-level OR expression references the same\n * left-hand column (e.g. `status = 'a' OR status = 'b' OR status = 'c'`).\n * Returns false when ORs span different columns — IN rewrite doesn't apply.\n */\nfunction allOrBranchesReferenceSameColumn(\n node: Extract<Node, { BoolExpr: unknown }>,\n): boolean {\n const columns = collectOrLeafColumns(node);\n if (columns.length === 0) return false;\n return columns.every((col) => col === columns[0]);\n}\n\nfunction collectOrLeafColumns(\n node: Extract<Node, { BoolExpr: unknown }>,\n): string[] {\n if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n return [];\n }\n\n const columns: string[] = [];\n for (const arg of node.BoolExpr.args) {\n if (!isANode(arg)) continue;\n if (is(arg, \"BoolExpr\") && arg.BoolExpr.boolop === \"OR_EXPR\") {\n columns.push(...collectOrLeafColumns(arg));\n } else if (is(arg, \"A_Expr\")) {\n const col = getLeftColumnKey(arg);\n if (col) {\n columns.push(col);\n } else {\n // Non-column comparison — can't be rewritten as IN\n return [];\n }\n } else {\n // Non-A_Expr leaf (e.g. SubLink, BoolExpr AND) — not a simple OR chain\n return [];\n }\n }\n return columns;\n}\n\n/**\n * Get a string key for the left-hand column of an A_Expr equality comparison.\n * For `t1.col = value` returns `\"t1.col\"`, for `col = value` returns `\"col\"`.\n */\nfunction getLeftColumnKey(\n expr: Extract<Node, { A_Expr: unknown }>,\n): string | null {\n if (!expr.A_Expr.lexpr || !isANode(expr.A_Expr.lexpr)) return null;\n if (!is(expr.A_Expr.lexpr, \"ColumnRef\")) return null;\n const fields = expr.A_Expr.lexpr.ColumnRef.fields;\n if (!fields) return null;\n return fields\n .filter(\n (f): f is Extract<Node, { String: unknown }> =>\n isANode(f) && is(f, \"String\"),\n )\n .map((f) => f.String.sval)\n .join(\".\");\n}\n\n/**\n * Check if a WHERE clause contains an equality between columns from different\n * tables (indicating an old-style implicit join condition).\n */\nfunction whereHasCrossTableEquality(\n whereClause: Node | undefined,\n tableNames: Set<string>,\n): boolean {\n if (!whereClause) return false;\n\n if (isANode(whereClause) && is(whereClause, \"A_Expr\")) {\n if (\n whereClause.A_Expr.kind === \"AEXPR_OP\" &&\n whereClause.A_Expr.name?.some(\n (n) => is(n, \"String\") && n.String.sval === \"=\",\n )\n ) {\n const leftTable = getColumnRefTableQualifier(whereClause.A_Expr.lexpr);\n const rightTable = getColumnRefTableQualifier(whereClause.A_Expr.rexpr);\n if (\n leftTable &&\n rightTable &&\n leftTable !== rightTable &&\n tableNames.has(leftTable) &&\n tableNames.has(rightTable)\n ) {\n return true;\n }\n }\n }\n\n // Recurse into AND/OR boolean expressions\n if (isANode(whereClause) && is(whereClause, \"BoolExpr\")) {\n for (const arg of whereClause.BoolExpr.args ?? []) {\n if (isANode(arg) && whereHasCrossTableEquality(arg, tableNames)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Extract the table qualifier (first field) from a ColumnRef node.\n * e.g. `t1.uid` → `\"t1\"`, `uid` → null\n */\nfunction getColumnRefTableQualifier(node: unknown): string | null {\n if (!node || !isANode(node as Node) || !is(node as Node, \"ColumnRef\"))\n return null;\n const fields = (node as Extract<Node, { ColumnRef: unknown }>).ColumnRef\n .fields;\n if (!fields || fields.length < 2) return null;\n const first = fields[0];\n if (isANode(first) && is(first, \"String\")) {\n return first.String.sval || null;\n }\n return null;\n}\n\nfunction isCountFuncCall(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"FuncCall\")) return false;\n const fc = node.FuncCall;\n const isCount =\n fc.funcname?.some((n) => is(n, \"String\") && n.String.sval === \"count\") ??\n false;\n if (!isCount) return false;\n // COUNT(*) has agg_star\n if (fc.agg_star) return true;\n // COUNT(1) or COUNT(literal) — single constant argument\n if (\n fc.args &&\n fc.args.length === 1 &&\n isANode(fc.args[0]) &&\n is(fc.args[0], \"A_Const\")\n ) {\n return true;\n }\n return false;\n}\n\nfunction isSubLinkWithCount(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n if (!isANode(node) || !is(node, \"SubLink\")) return false;\n const sub = node.SubLink;\n const subselect = sub.subselect;\n if (!subselect || !isANode(subselect) || !is(subselect, \"SelectStmt\"))\n return false;\n const targets = subselect.SelectStmt.targetList;\n if (!targets || targets.length !== 1) return false;\n const target = targets[0];\n if (!isANode(target) || !is(target, \"ResTarget\") || !target.ResTarget.val)\n return false;\n return isCountFuncCall(target.ResTarget.val);\n}\n\nfunction isCountExpression(node: unknown): boolean {\n return isCountFuncCall(node) || isSubLinkWithCount(node);\n}\n\nfunction getIntegerConstantValue(node: unknown): number | null {\n if (!node || typeof node !== \"object\") return null;\n if (!isANode(node) || !is(node, \"A_Const\")) return null;\n if (node.A_Const.ival === undefined) return null;\n // protobuf: ival: {} means 0, ival: { ival: N } means N\n return node.A_Const.ival.ival ?? 0;\n}\n\nfunction isExistenceCheckPattern(\n lexpr: unknown,\n rexpr: unknown,\n op: string,\n): boolean {\n // count_expr > 0, count_expr >= 1, count_expr != 0, count_expr <> 0\n if (isCountExpression(lexpr)) {\n const val = getIntegerConstantValue(rexpr);\n if (val !== null) {\n if (op === \">\" && val === 0) return true;\n if (op === \">=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n // Reversed: 0 < count_expr, 1 <= count_expr, 0 != count_expr\n if (isCountExpression(rexpr)) {\n const val = getIntegerConstantValue(lexpr);\n if (val !== null) {\n if (op === \"<\" && val === 0) return true;\n if (op === \"<=\" && val === 1) return true;\n if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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"}
|