@pgsql/transform 18.15.2 → 18.15.3

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/esm/transform.js CHANGED
@@ -197,7 +197,9 @@ export function transformSchemaRefsInString(str, schemaMapping, result) {
197
197
  // References embedded in opaque strings carry no object identity, so only
198
198
  // whole-schema (schema-level default) routes can be applied here.
199
199
  for (const [oldSchema, newSchema] of schemaLevelMap(schemaMapping)) {
200
- const pattern = new RegExp(`(?<![\\w-])("?)${escapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
200
+ if (!out.includes(oldSchema))
201
+ continue;
202
+ const pattern = cachedRegExp(`(?<![\\w-])("?)${cachedEscapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
201
203
  const before = out;
202
204
  out = out.replace(pattern, `$1${newSchema}$1`);
203
205
  if (out !== before) {
@@ -618,14 +620,19 @@ export function validateNoUntransformedSchemas(content, schemaMapping) {
618
620
  if (moved.size === 0) {
619
621
  return;
620
622
  }
623
+ // Every pattern below requires a literal occurrence of the schema name, so a
624
+ // single case-insensitive substring scan rules out most schemas up front.
625
+ const lowerContent = content.toLowerCase();
621
626
  for (const [oldSchema, newSchema] of moved) {
622
- const escapedSchema = escapeRegexp(oldSchema);
627
+ if (!lowerContent.includes(oldSchema.toLowerCase()))
628
+ continue;
629
+ const escapedSchema = cachedEscapeRegexp(oldSchema);
623
630
  // Pattern 1: quoted or unquoted schema name followed by dot (schema-qualified)
624
- const dotPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
631
+ const dotPattern = cachedRegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
625
632
  // Pattern 2: standalone schema name in known SQL contexts
626
633
  // Note: We don't use trailing \b because it fails after closing quotes
627
634
  // (both '"' and whitespace are non-word characters, so no boundary exists).
628
- const standalonePattern = new RegExp(`(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
635
+ const standalonePattern = cachedRegExp(`(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
629
636
  `(?:"${escapedSchema}"|\\b${escapedSchema}\\b)`, 'gi');
630
637
  const dotMatches = content.match(dotPattern);
631
638
  const standaloneMatches = content.match(standalonePattern);
@@ -633,7 +640,7 @@ export function validateNoUntransformedSchemas(content, schemaMapping) {
633
640
  if (totalMatches > 0) {
634
641
  const lines = content.split('\n');
635
642
  const locations = [];
636
- const combinedPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)|` +
643
+ const combinedPattern = cachedRegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)|` +
637
644
  `(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
638
645
  `(?:"${escapedSchema}"|\\b${escapedSchema}\\b)`, 'gi');
639
646
  for (let i = 0; i < lines.length; i++) {
@@ -878,6 +885,34 @@ function transformSchemasInContext(ctx, schemaMapping, result, visitorOptions) {
878
885
  export function escapeRegexp(str) {
879
886
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
880
887
  }
888
+ /**
889
+ * Compiling a pattern costs far more than running it, and the string-level
890
+ * passes below rebuild the same handful of per-schema patterns for every file
891
+ * they touch — on a large corpus that is hundreds of thousands of identical
892
+ * compiles. Patterns are keyed by source + flags and reused; each returned
893
+ * RegExp is stateful (`g`/`y` carry `lastIndex`), so callers must only use it
894
+ * with `String#replace`/`String#match`, which reset it, or reset it
895
+ * themselves, exactly as the throwaway instances required.
896
+ */
897
+ const regExpCache = new Map();
898
+ function cachedRegExp(source, flags) {
899
+ const key = `${flags}\u0000${source}`;
900
+ let re = regExpCache.get(key);
901
+ if (!re) {
902
+ re = new RegExp(source, flags);
903
+ regExpCache.set(key, re);
904
+ }
905
+ return re;
906
+ }
907
+ const escapeRegexpCache = new Map();
908
+ function cachedEscapeRegexp(str) {
909
+ let escaped = escapeRegexpCache.get(str);
910
+ if (escaped === undefined) {
911
+ escaped = escapeRegexp(str);
912
+ escapeRegexpCache.set(str, escaped);
913
+ }
914
+ return escaped;
915
+ }
881
916
  /**
882
917
  * Extract pgpm header comments from the beginning of SQL content.
883
918
  */
@@ -936,7 +971,7 @@ export function transformComments(content, schemaMapping, result) {
936
971
  if (!newName)
937
972
  continue;
938
973
  result.schemasFound.add(schema);
939
- const pathPattern = new RegExp(`(schemas/)${escapeRegexp(schema)}(/|$)`, 'g');
974
+ const pathPattern = cachedRegExp(`(schemas/)${cachedEscapeRegexp(schema)}(/|$)`, 'g');
940
975
  const before = newPath;
941
976
  newPath = newPath.replace(pathPattern, `$1${newName}$2`);
942
977
  if (newPath !== before) {
@@ -953,15 +988,20 @@ export function transformComments(content, schemaMapping, result) {
953
988
  export function transformVerifyCalls(content, schemaMapping, result) {
954
989
  const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
955
990
  let newContent = content;
991
+ // The pattern needs the schema name spelled out, case-insensitively.
992
+ let lowerContent = content.toLowerCase();
956
993
  for (const schema of schemas) {
957
994
  const newName = schemaMapping.get(schema);
958
995
  if (!newName)
959
996
  continue;
960
- const escapedSchema = escapeRegexp(schema);
961
- const verifyPattern = new RegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
997
+ if (!lowerContent.includes(schema.toLowerCase()))
998
+ continue;
999
+ const escapedSchema = cachedEscapeRegexp(schema);
1000
+ const verifyPattern = cachedRegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
962
1001
  const before = newContent;
963
1002
  newContent = newContent.replace(verifyPattern, `$1${newName}$2`);
964
1003
  if (newContent !== before) {
1004
+ lowerContent = newContent.toLowerCase();
965
1005
  result.schemasFound.add(schema);
966
1006
  result.schemasTransformed.set(schema, newName);
967
1007
  }
@@ -978,8 +1018,10 @@ export function transformJsonStringValues(content, schemaMapping, result) {
978
1018
  const newName = schemaMapping.get(schema);
979
1019
  if (!newName)
980
1020
  continue;
981
- const escapedSchema = escapeRegexp(schema);
982
- const jsonValuePattern = new RegExp(`(:")${escapedSchema}(")`, 'g');
1021
+ if (!newContent.includes(schema))
1022
+ continue;
1023
+ const escapedSchema = cachedEscapeRegexp(schema);
1024
+ const jsonValuePattern = cachedRegExp(`(:")${escapedSchema}(")`, 'g');
983
1025
  const before = newContent;
984
1026
  newContent = newContent.replace(jsonValuePattern, `$1${newName}$2`);
985
1027
  if (newContent !== before) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgsql/transform",
3
- "version": "18.15.2",
3
+ "version": "18.15.3",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "AST-based SQL transformation, qualification, classification and closure analysis for PostgreSQL",
6
6
  "main": "index.js",
@@ -50,5 +50,5 @@
50
50
  "classify",
51
51
  "plpgsql"
52
52
  ],
53
- "gitHead": "b6105874628cc7b8517c1ebbd99e9ad6fe786976"
53
+ "gitHead": "69cdce673db035fb1ee3b529480d39fac01512ab"
54
54
  }
package/transform.js CHANGED
@@ -218,7 +218,9 @@ function transformSchemaRefsInString(str, schemaMapping, result) {
218
218
  // References embedded in opaque strings carry no object identity, so only
219
219
  // whole-schema (schema-level default) routes can be applied here.
220
220
  for (const [oldSchema, newSchema] of schemaLevelMap(schemaMapping)) {
221
- const pattern = new RegExp(`(?<![\\w-])("?)${escapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
221
+ if (!out.includes(oldSchema))
222
+ continue;
223
+ const pattern = cachedRegExp(`(?<![\\w-])("?)${cachedEscapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
222
224
  const before = out;
223
225
  out = out.replace(pattern, `$1${newSchema}$1`);
224
226
  if (out !== before) {
@@ -639,14 +641,19 @@ function validateNoUntransformedSchemas(content, schemaMapping) {
639
641
  if (moved.size === 0) {
640
642
  return;
641
643
  }
644
+ // Every pattern below requires a literal occurrence of the schema name, so a
645
+ // single case-insensitive substring scan rules out most schemas up front.
646
+ const lowerContent = content.toLowerCase();
642
647
  for (const [oldSchema, newSchema] of moved) {
643
- const escapedSchema = escapeRegexp(oldSchema);
648
+ if (!lowerContent.includes(oldSchema.toLowerCase()))
649
+ continue;
650
+ const escapedSchema = cachedEscapeRegexp(oldSchema);
644
651
  // Pattern 1: quoted or unquoted schema name followed by dot (schema-qualified)
645
- const dotPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
652
+ const dotPattern = cachedRegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
646
653
  // Pattern 2: standalone schema name in known SQL contexts
647
654
  // Note: We don't use trailing \b because it fails after closing quotes
648
655
  // (both '"' and whitespace are non-word characters, so no boundary exists).
649
- const standalonePattern = new RegExp(`(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
656
+ const standalonePattern = cachedRegExp(`(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
650
657
  `(?:"${escapedSchema}"|\\b${escapedSchema}\\b)`, 'gi');
651
658
  const dotMatches = content.match(dotPattern);
652
659
  const standaloneMatches = content.match(standalonePattern);
@@ -654,7 +661,7 @@ function validateNoUntransformedSchemas(content, schemaMapping) {
654
661
  if (totalMatches > 0) {
655
662
  const lines = content.split('\n');
656
663
  const locations = [];
657
- const combinedPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)|` +
664
+ const combinedPattern = cachedRegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)|` +
658
665
  `(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
659
666
  `(?:"${escapedSchema}"|\\b${escapedSchema}\\b)`, 'gi');
660
667
  for (let i = 0; i < lines.length; i++) {
@@ -899,6 +906,34 @@ function transformSchemasInContext(ctx, schemaMapping, result, visitorOptions) {
899
906
  function escapeRegexp(str) {
900
907
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
901
908
  }
909
+ /**
910
+ * Compiling a pattern costs far more than running it, and the string-level
911
+ * passes below rebuild the same handful of per-schema patterns for every file
912
+ * they touch — on a large corpus that is hundreds of thousands of identical
913
+ * compiles. Patterns are keyed by source + flags and reused; each returned
914
+ * RegExp is stateful (`g`/`y` carry `lastIndex`), so callers must only use it
915
+ * with `String#replace`/`String#match`, which reset it, or reset it
916
+ * themselves, exactly as the throwaway instances required.
917
+ */
918
+ const regExpCache = new Map();
919
+ function cachedRegExp(source, flags) {
920
+ const key = `${flags}\u0000${source}`;
921
+ let re = regExpCache.get(key);
922
+ if (!re) {
923
+ re = new RegExp(source, flags);
924
+ regExpCache.set(key, re);
925
+ }
926
+ return re;
927
+ }
928
+ const escapeRegexpCache = new Map();
929
+ function cachedEscapeRegexp(str) {
930
+ let escaped = escapeRegexpCache.get(str);
931
+ if (escaped === undefined) {
932
+ escaped = escapeRegexp(str);
933
+ escapeRegexpCache.set(str, escaped);
934
+ }
935
+ return escaped;
936
+ }
902
937
  /**
903
938
  * Extract pgpm header comments from the beginning of SQL content.
904
939
  */
@@ -957,7 +992,7 @@ function transformComments(content, schemaMapping, result) {
957
992
  if (!newName)
958
993
  continue;
959
994
  result.schemasFound.add(schema);
960
- const pathPattern = new RegExp(`(schemas/)${escapeRegexp(schema)}(/|$)`, 'g');
995
+ const pathPattern = cachedRegExp(`(schemas/)${cachedEscapeRegexp(schema)}(/|$)`, 'g');
961
996
  const before = newPath;
962
997
  newPath = newPath.replace(pathPattern, `$1${newName}$2`);
963
998
  if (newPath !== before) {
@@ -974,15 +1009,20 @@ function transformComments(content, schemaMapping, result) {
974
1009
  function transformVerifyCalls(content, schemaMapping, result) {
975
1010
  const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
976
1011
  let newContent = content;
1012
+ // The pattern needs the schema name spelled out, case-insensitively.
1013
+ let lowerContent = content.toLowerCase();
977
1014
  for (const schema of schemas) {
978
1015
  const newName = schemaMapping.get(schema);
979
1016
  if (!newName)
980
1017
  continue;
981
- const escapedSchema = escapeRegexp(schema);
982
- const verifyPattern = new RegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
1018
+ if (!lowerContent.includes(schema.toLowerCase()))
1019
+ continue;
1020
+ const escapedSchema = cachedEscapeRegexp(schema);
1021
+ const verifyPattern = cachedRegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
983
1022
  const before = newContent;
984
1023
  newContent = newContent.replace(verifyPattern, `$1${newName}$2`);
985
1024
  if (newContent !== before) {
1025
+ lowerContent = newContent.toLowerCase();
986
1026
  result.schemasFound.add(schema);
987
1027
  result.schemasTransformed.set(schema, newName);
988
1028
  }
@@ -999,8 +1039,10 @@ function transformJsonStringValues(content, schemaMapping, result) {
999
1039
  const newName = schemaMapping.get(schema);
1000
1040
  if (!newName)
1001
1041
  continue;
1002
- const escapedSchema = escapeRegexp(schema);
1003
- const jsonValuePattern = new RegExp(`(:")${escapedSchema}(")`, 'g');
1042
+ if (!newContent.includes(schema))
1043
+ continue;
1044
+ const escapedSchema = cachedEscapeRegexp(schema);
1045
+ const jsonValuePattern = cachedRegExp(`(:")${escapedSchema}(")`, 'g');
1004
1046
  const before = newContent;
1005
1047
  newContent = newContent.replace(jsonValuePattern, `$1${newName}$2`);
1006
1048
  if (newContent !== before) {