prostgles-server 4.2.545 → 4.2.546

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/DboBuilder/DboBuilder.js.map +1 -1
  2. package/dist/DboBuilder/QueryBuilder/Functions/Functions.d.ts.map +1 -1
  3. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js +19 -376
  4. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js.map +1 -1
  5. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.d.ts +3 -0
  6. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.d.ts.map +1 -0
  7. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js +107 -0
  8. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js.map +1 -0
  9. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.d.ts +3 -0
  10. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.d.ts.map +1 -0
  11. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.js +276 -0
  12. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.js.map +1 -0
  13. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts +3 -0
  14. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts.map +1 -0
  15. package/dist/DboBuilder/QueryBuilder/Functions/utils.js +6 -0
  16. package/dist/DboBuilder/QueryBuilder/Functions/utils.js.map +1 -0
  17. package/dist/DboBuilder/TableHandler/update.js.map +1 -1
  18. package/dist/DboBuilder/ViewHandler/getExistsCondition.js +5 -5
  19. package/dist/DboBuilder/ViewHandler/getExistsCondition.js.map +1 -1
  20. package/dist/DboBuilder/ViewHandler/parseFieldFilter.js.map +1 -1
  21. package/dist/DboBuilder/runSql/runSQL.js +1 -1
  22. package/dist/DboBuilder/runSql/runSQL.js.map +1 -1
  23. package/dist/FileManager/FileManager.js.map +1 -1
  24. package/dist/Logging.d.ts +1 -1
  25. package/dist/Logging.d.ts.map +1 -1
  26. package/dist/PubSubManager/PubSubManager.d.ts +0 -1
  27. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  28. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  29. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.d.ts +7 -2
  30. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.d.ts.map +1 -1
  31. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.js +21 -16
  32. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.js.map +1 -1
  33. package/dist/PubSubManager/SyncReplication/syncData.d.ts.map +1 -1
  34. package/dist/PubSubManager/SyncReplication/syncData.js +16 -14
  35. package/dist/PubSubManager/SyncReplication/syncData.js.map +1 -1
  36. package/dist/TableConfig/TableConfig.js.map +1 -1
  37. package/dist/runClientRequest.js +1 -1
  38. package/dist/runClientRequest.js.map +1 -1
  39. package/lib/DboBuilder/DboBuilder.ts +1 -1
  40. package/lib/DboBuilder/QueryBuilder/Functions/Functions.ts +142 -557
  41. package/lib/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.ts +120 -0
  42. package/lib/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.ts +302 -0
  43. package/lib/DboBuilder/QueryBuilder/Functions/utils.ts +3 -0
  44. package/lib/DboBuilder/TableHandler/update.ts +1 -1
  45. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +4 -4
  46. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +1 -1
  47. package/lib/DboBuilder/runSql/runSQL.ts +1 -1
  48. package/lib/FileManager/FileManager.ts +1 -1
  49. package/lib/Logging.ts +1 -1
  50. package/lib/PubSubManager/PubSubManager.ts +0 -1
  51. package/lib/PubSubManager/SyncReplication/getSyncUtilFunctions.ts +28 -19
  52. package/lib/PubSubManager/SyncReplication/syncData.ts +16 -14
  53. package/lib/TableConfig/TableConfig.ts +1 -1
  54. package/lib/runClientRequest.ts +1 -1
  55. package/package.json +2 -2
@@ -1,15 +1,10 @@
1
- import type { ColumnInfo, PG_COLUMN_UDT_DATA_TYPE } from "prostgles-types";
2
- import {
3
- asName,
4
- includes,
5
- isEmpty,
6
- isObject,
7
- TextFilter_FullTextSearchFilterKeys,
8
- postgresToTsType,
9
- } from "prostgles-types";
10
- import { parseFieldFilter } from "../../ViewHandler/parseFieldFilter";
11
1
  import * as pgPromise from "pg-promise";
2
+ import type { ColumnInfo, PG_COLUMN_UDT_DATA_TYPE } from "prostgles-types";
3
+ import { asName, includes, isObject, TextFilter_FullTextSearchFilterKeys } from "prostgles-types";
12
4
  import { asNameAlias } from "../../../utils/asNameAlias";
5
+ import { HASHING_FUNCTIONS } from "./HASHING_FUNCTIONS";
6
+ import { TEXT_FUNCTIONS } from "./TEXT_FUNCTIONS";
7
+ import { asFunction } from "./utils";
13
8
  const pgp = pgPromise();
14
9
 
15
10
  type GetQueryArgs = {
@@ -74,7 +69,6 @@ export type FunctionSpec = {
74
69
  returnType?: PG_COLUMN_UDT_DATA_TYPE;
75
70
  };
76
71
 
77
- const MAX_COL_NUM = 1600;
78
72
  const asValue = (v: any, castAs = "") => pgp.as.format("$1" + castAs, [v]);
79
73
 
80
74
  const parseUnix = (
@@ -195,7 +189,7 @@ const JSON_Funcs: FunctionSpec[] = [
195
189
  const escapedName = asNameAlias(colName, tableAlias);
196
190
  return `${name}(${escapedName})`;
197
191
  },
198
- }) as FunctionSpec,
192
+ }) satisfies FunctionSpec,
199
193
  ),
200
194
  ];
201
195
 
@@ -494,116 +488,7 @@ PostGIS_Funcs = PostGIS_Funcs.concat(
494
488
  * Each function expects a column at the very least
495
489
  */
496
490
  export const FUNCTIONS: FunctionSpec[] = [
497
- // Hashing
498
- {
499
- name: "$md5_multi",
500
- description: ` :[...column_names] -> md5 hash of the column content`,
501
- type: "function",
502
- singleColArg: false,
503
- numArgs: MAX_COL_NUM,
504
- getFields: (args: any[]) => args,
505
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
506
- const q = pgp.as.format(
507
- "md5(" +
508
- args
509
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + "::text, '' )")
510
- .join(" || ") +
511
- ")",
512
- );
513
- return q;
514
- },
515
- },
516
- {
517
- name: "$md5_multi_agg",
518
- description: ` :[...column_names] -> md5 hash of the string aggregation of column content`,
519
- type: "aggregation",
520
- singleColArg: false,
521
- numArgs: MAX_COL_NUM,
522
- getFields: (args: any[]) => args,
523
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
524
- const q = pgp.as.format(
525
- "md5(string_agg(" +
526
- args
527
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + "::text, '' )")
528
- .join(" || ") +
529
- ", ','))",
530
- );
531
- return q;
532
- },
533
- },
534
-
535
- {
536
- name: "$sha256_multi",
537
- description: ` :[...column_names] -> sha256 hash of the of column content`,
538
- type: "function",
539
- singleColArg: false,
540
- numArgs: MAX_COL_NUM,
541
- getFields: (args: any[]) => args,
542
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
543
- const q = pgp.as.format(
544
- "encode(sha256((" +
545
- args
546
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
547
- .join(" || ") +
548
- ")::text::bytea), 'hex')",
549
- );
550
- return q;
551
- },
552
- },
553
- {
554
- name: "$sha256_multi_agg",
555
- description: ` :[...column_names] -> sha256 hash of the string aggregation of column content`,
556
- type: "aggregation",
557
- singleColArg: false,
558
- numArgs: MAX_COL_NUM,
559
- getFields: (args: any[]) => args,
560
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
561
- const q = pgp.as.format(
562
- "encode(sha256(string_agg(" +
563
- args
564
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
565
- .join(" || ") +
566
- ", ',')::text::bytea), 'hex')",
567
- );
568
- return q;
569
- },
570
- },
571
- {
572
- name: "$sha512_multi",
573
- description: ` :[...column_names] -> sha512 hash of the of column content`,
574
- type: "function",
575
- singleColArg: false,
576
- numArgs: MAX_COL_NUM,
577
- getFields: (args: any[]) => args,
578
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
579
- const q = pgp.as.format(
580
- "encode(sha512((" +
581
- args
582
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
583
- .join(" || ") +
584
- ")::text::bytea), 'hex')",
585
- );
586
- return q;
587
- },
588
- },
589
- {
590
- name: "$sha512_multi_agg",
591
- description: ` :[...column_names] -> sha512 hash of the string aggregation of column content`,
592
- type: "aggregation",
593
- singleColArg: false,
594
- numArgs: MAX_COL_NUM,
595
- getFields: (args: any[]) => args,
596
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
597
- const q = pgp.as.format(
598
- "encode(sha512(string_agg(" +
599
- args
600
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
601
- .join(" || ") +
602
- ", ',')::text::bytea), 'hex')",
603
- );
604
- return q;
605
- },
606
- },
491
+ ...HASHING_FUNCTIONS,
607
492
 
608
493
  ...FTS_Funcs,
609
494
 
@@ -611,78 +496,7 @@ export const FUNCTIONS: FunctionSpec[] = [
611
496
 
612
497
  ...PostGIS_Funcs,
613
498
 
614
- {
615
- name: "$left",
616
- description: ` :[column_name, number] -> substring`,
617
- type: "function",
618
- numArgs: 2,
619
- singleColArg: false,
620
- getFields: (args: any[]) => [args[0]],
621
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
622
- return pgp.as.format("LEFT(" + asNameAlias(args[0], tableAlias) + ", $1)", [args[1]]);
623
- },
624
- },
625
- {
626
- name: "$column",
627
- description: ` :[column_name] -> Returns the column value as is`,
628
- type: "function",
629
- numArgs: 1,
630
- singleColArg: false,
631
- getFields: (args: any[]) => [args[0]],
632
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
633
- const aliasedColumnName = args[0];
634
- if (!aliasedColumnName) {
635
- throw `$column: column_name is required`;
636
- }
637
- return pgp.as.format(asNameAlias(aliasedColumnName, tableAlias));
638
- },
639
- },
640
- {
641
- name: "$unnest_words",
642
- description: ` :[column_name] -> Splits string at spaces`,
643
- type: "function",
644
- numArgs: 1,
645
- singleColArg: true,
646
- getFields: (args: any[]) => [args[0]],
647
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
648
- return pgp.as.format(
649
- "unnest(string_to_array(" + asNameAlias(args[0], tableAlias) + "::TEXT , ' '))",
650
- ); //, [args[1]]
651
- },
652
- },
653
- {
654
- name: "$right",
655
- description: ` :[column_name, number] -> substring`,
656
- type: "function",
657
- numArgs: 2,
658
- singleColArg: false,
659
- getFields: (args: any[]) => [args[0]],
660
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
661
- return pgp.as.format("RIGHT(" + asNameAlias(args[0], tableAlias) + ", $1)", [args[1]]);
662
- },
663
- },
664
-
665
- {
666
- name: "$to_char",
667
- type: "function",
668
- description: ` :[column_name, format<string>] -> format dates and strings. Eg: [current_timestamp, 'HH12:MI:SS']`,
669
- singleColArg: false,
670
- numArgs: 2,
671
- getFields: (args: any[]) => [args[0]],
672
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
673
- if (args.length === 3) {
674
- return pgp.as.format("to_char(" + asNameAlias(args[0], tableAlias) + ", $2, $3)", [
675
- args[0],
676
- args[1],
677
- args[2],
678
- ]);
679
- }
680
- return pgp.as.format("to_char(" + asNameAlias(args[0], tableAlias) + ", $2)", [
681
- args[0],
682
- args[1],
683
- ]);
684
- },
685
- },
499
+ ...TEXT_FUNCTIONS,
686
500
 
687
501
  /**
688
502
  * Date trunc utils
@@ -736,64 +550,62 @@ export const FUNCTIONS: FunctionSpec[] = [
736
550
  { val: 5, unit: "millisecond" },
737
551
  { val: 2, unit: "millisecond" },
738
552
  ])
739
- .map(
740
- ({ val, unit }) =>
741
- ({
742
- name: "$date_trunc_" + (val || "") + unit,
743
- type: "function",
744
- description: ` :[column_name, opts?: { timeZone: true | 'TZ Name' }] -> round down timestamp to closest ${val || ""} ${unit} `,
745
- singleColArg: true,
746
- numArgs: 2,
747
- getFields: (args: any[]) => [args[0]],
748
- getQuery: ({ allColumns, args, tableAliasRaw: tableAlias }) => {
749
- /** Timestamp added to ensure filters work correctly (psql will loose the string value timezone when comparing to a non tz column) */
750
- const col = parseUnix(args[0], tableAlias, allColumns, args[1]);
751
- if (!val) return `date_trunc(${asValue(unit)}, ${col})`;
752
- const PreviousUnit = {
753
- year: "decade",
754
- month: "year",
755
- hour: "day",
756
- minute: "hour",
757
- second: "minute",
758
- millisecond: "second",
759
- microsecond: "millisecond",
760
- };
761
-
762
- const prevUnit = PreviousUnit[unit as "month"];
763
- if (!prevUnit) {
764
- throw "Not supported. prevUnit not found";
765
- }
766
-
767
- let extractedUnit = `date_part(${asValue(unit, "::text")}, ${col})::int`;
768
- if (unit === "microsecond" || unit === "millisecond") {
769
- extractedUnit = `(${extractedUnit} - 1000 * floor(${extractedUnit}/1000)::int)`;
770
- }
771
- const res = `(date_trunc(${asValue(prevUnit)}, ${col}) + floor(${extractedUnit} / ${val}) * interval ${asValue(val + " " + unit)})`;
772
- // console.log(res);
773
- return res;
774
- },
775
- }) as FunctionSpec,
776
- ),
777
-
778
- /* Date funcs date_part */
779
- ...["date_trunc", "date_part"].map(
780
- (funcName) =>
781
- ({
782
- name: "$" + funcName,
553
+ .map(({ val, unit }) =>
554
+ asFunction({
555
+ name: "$date_trunc_" + (val || "") + unit,
783
556
  type: "function",
784
- numArgs: 3,
785
- description:
786
- ` :[unit<string>, column_name, opts?: { timeZone: true | string }] -> ` +
787
- (funcName === "date_trunc" ?
788
- ` round down timestamp to closest unit value. `
789
- : ` extract date unit as float8. `) +
790
- ` E.g. ['hour', col] `,
791
- singleColArg: false,
792
- getFields: (args: any[]) => [args[1]],
557
+ description: ` :[column_name, opts?: { timeZone: true | 'TZ Name' }] -> round down timestamp to closest ${val || ""} ${unit} `,
558
+ singleColArg: true,
559
+ numArgs: 2,
560
+ getFields: (args: any[]) => [args[0]],
793
561
  getQuery: ({ allColumns, args, tableAliasRaw: tableAlias }) => {
794
- return `${funcName}(${asValue(args[0])}, ${parseUnix(args[1], tableAlias, allColumns, args[2])})`;
562
+ /** Timestamp added to ensure filters work correctly (psql will loose the string value timezone when comparing to a non tz column) */
563
+ const col = parseUnix(args[0], tableAlias, allColumns, args[1]);
564
+ if (!val) return `date_trunc(${asValue(unit)}, ${col})`;
565
+ const PreviousUnit = {
566
+ year: "decade",
567
+ month: "year",
568
+ hour: "day",
569
+ minute: "hour",
570
+ second: "minute",
571
+ millisecond: "second",
572
+ microsecond: "millisecond",
573
+ };
574
+
575
+ const prevUnit = PreviousUnit[unit as "month"];
576
+ if (!prevUnit) {
577
+ throw "Not supported. prevUnit not found";
578
+ }
579
+
580
+ let extractedUnit = `date_part(${asValue(unit, "::text")}, ${col})::int`;
581
+ if (unit === "microsecond" || unit === "millisecond") {
582
+ extractedUnit = `(${extractedUnit} - 1000 * floor(${extractedUnit}/1000)::int)`;
583
+ }
584
+ const res = `(date_trunc(${asValue(prevUnit)}, ${col}) + floor(${extractedUnit} / ${val}) * interval ${asValue(val + " " + unit)})`;
585
+ // console.log(res);
586
+ return res;
795
587
  },
796
- }) as FunctionSpec,
588
+ }),
589
+ ),
590
+
591
+ /* Date funcs date_part */
592
+ ...["date_trunc", "date_part"].map((funcName) =>
593
+ asFunction({
594
+ name: "$" + funcName,
595
+ type: "function",
596
+ numArgs: 3,
597
+ description:
598
+ ` :[unit<string>, column_name, opts?: { timeZone: true | string }] -> ` +
599
+ (funcName === "date_trunc" ?
600
+ ` round down timestamp to closest unit value. `
601
+ : ` extract date unit as float8. `) +
602
+ ` E.g. ['hour', col] `,
603
+ singleColArg: false,
604
+ getFields: (args: any[]) => [args[1]],
605
+ getQuery: ({ allColumns, args, tableAliasRaw: tableAlias }) => {
606
+ return `${funcName}(${asValue(args[0])}, ${parseUnix(args[1], tableAlias, allColumns, args[2])})`;
607
+ },
608
+ }),
797
609
  ),
798
610
 
799
611
  /* Handy date funcs */
@@ -832,23 +644,22 @@ export const FUNCTIONS: FunctionSpec[] = [
832
644
  ["yyyy", "yyyy"],
833
645
  ["yy", "yy"],
834
646
  ["yr", "yy"],
835
- ].map(
836
- ([funcName, txt]) =>
837
- ({
838
- name: "$" + funcName,
839
- type: "function",
840
- description:
841
- ` :[column_name, opts?: { timeZone: true | string }] -> get timestamp formated as ` + txt,
842
- singleColArg: true,
843
- numArgs: 1,
844
- getFields: (args: any[]) => [args[0]],
845
- getQuery: ({ allColumns, args, tableAliasRaw: tableAlias }) => {
846
- return pgp.as.format(
847
- "trim(to_char(" + parseUnix(args[0], tableAlias, allColumns, args[1]) + ", $2))",
848
- [args[0], txt],
849
- );
850
- },
851
- }) as FunctionSpec,
647
+ ].map(([funcName, txt]) =>
648
+ asFunction({
649
+ name: "$" + funcName,
650
+ type: "function",
651
+ description:
652
+ ` :[column_name, opts?: { timeZone: true | string }] -> get timestamp formated as ` + txt,
653
+ singleColArg: true,
654
+ numArgs: 1,
655
+ getFields: (args: any[]) => [args[0]],
656
+ getQuery: ({ allColumns, args, tableAliasRaw: tableAlias }) => {
657
+ return pgp.as.format(
658
+ "trim(to_char(" + parseUnix(args[0], tableAlias, allColumns, args[1]) + ", $2))",
659
+ [args[0], txt],
660
+ );
661
+ },
662
+ }),
852
663
  ),
853
664
 
854
665
  /* Basic 1 arg col funcs */
@@ -865,305 +676,79 @@ export const FUNCTIONS: FunctionSpec[] = [
865
676
  funcName,
866
677
  })),
867
678
  ),
868
- ].map(
869
- ({ funcName, cast }) =>
870
- ({
871
- name: "$" + funcName,
872
- type: "function",
873
- numArgs: 1,
874
- singleColArg: true,
875
- getFields: (args: any[]) => [args[0]],
876
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
877
- return `${funcName}(${asNameAlias(args[0], tableAlias)}${cast ? `::${cast}` : ""})`;
878
- },
879
- }) as FunctionSpec,
679
+ ].map(({ funcName, cast }) =>
680
+ asFunction({
681
+ name: "$" + funcName,
682
+ type: "function",
683
+ numArgs: 1,
684
+ singleColArg: true,
685
+ getFields: (args: any[]) => [args[0]],
686
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
687
+ return `${funcName}(${asNameAlias(args[0], tableAlias)}${cast ? `::${cast}` : ""})`;
688
+ },
689
+ }),
880
690
  ),
881
691
 
882
692
  /**
883
693
  * Interval funcs
884
694
  * (col1, col2?, trunc )
885
695
  * */
886
- ...(["age", "ageNow", "difference"] as const).map(
887
- (funcName) =>
888
- ({
889
- name: "$" + funcName,
890
- type: "function",
891
- numArgs: 2,
892
- singleColArg: true,
893
- // Filtered because the second arg is optional
894
- getFields: (args: any[]) => args.slice(0, 2).filter((a) => typeof a === "string"),
895
- getQuery: ({ args, tableAliasRaw: tableAlias, allColumns }) => {
896
- const validColCount = args.slice(0, 2).filter((a) => typeof a === "string").length;
897
- const trunc = args[2];
898
- const allowedTruncs = ["second", "minute", "hour", "day", "month", "year"];
899
- if (trunc && !allowedTruncs.includes(trunc))
900
- throw new Error(
901
- "Incorrect trunc provided. Allowed values: " + allowedTruncs.join(", "),
902
- );
903
- if (funcName === "difference" && validColCount !== 2)
904
- throw new Error("Must have two column names");
905
- if (![1, 2].includes(validColCount)) throw new Error("Must have one or two column names");
906
- const [leftField, rightField] = args as [string, string];
907
- const tzOpts = args[2];
908
- const leftQ = parseUnix(leftField, tableAlias, allColumns, tzOpts);
909
- let rightQ = rightField ? parseUnix(rightField, tableAlias, allColumns, tzOpts) : "";
910
- let query = "";
911
- if (funcName === "ageNow" && validColCount === 1) {
912
- query = `age(now(), ${leftQ})`;
913
- } else if (funcName === "age" || funcName === "ageNow") {
914
- if (rightQ) rightQ = ", " + rightQ;
915
- query = `age(${leftQ} ${rightQ})`;
916
- } else {
917
- query = `${leftQ} - ${rightQ}`;
918
- }
919
- return trunc ? `date_trunc(${asValue(trunc)}, ${query})` : query;
920
- },
921
- }) as FunctionSpec,
696
+ ...(["age", "ageNow", "difference"] as const).map((funcName) =>
697
+ asFunction({
698
+ name: "$" + funcName,
699
+ type: "function",
700
+ numArgs: 2,
701
+ singleColArg: true,
702
+ // Filtered because the second arg is optional
703
+ getFields: (args: any[]) => args.slice(0, 2).filter((a) => typeof a === "string"),
704
+ getQuery: ({ args, tableAliasRaw: tableAlias, allColumns }) => {
705
+ const validColCount = args.slice(0, 2).filter((a) => typeof a === "string").length;
706
+ const trunc = args[2];
707
+ const allowedTruncs = ["second", "minute", "hour", "day", "month", "year"];
708
+ if (trunc && !allowedTruncs.includes(trunc))
709
+ throw new Error("Incorrect trunc provided. Allowed values: " + allowedTruncs.join(", "));
710
+ if (funcName === "difference" && validColCount !== 2)
711
+ throw new Error("Must have two column names");
712
+ if (![1, 2].includes(validColCount)) throw new Error("Must have one or two column names");
713
+ const [leftField, rightField] = args as [string, string];
714
+ const tzOpts = args[2];
715
+ const leftQ = parseUnix(leftField, tableAlias, allColumns, tzOpts);
716
+ let rightQ = rightField ? parseUnix(rightField, tableAlias, allColumns, tzOpts) : "";
717
+ let query = "";
718
+ if (funcName === "ageNow" && validColCount === 1) {
719
+ query = `age(now(), ${leftQ})`;
720
+ } else if (funcName === "age" || funcName === "ageNow") {
721
+ if (rightQ) rightQ = ", " + rightQ;
722
+ query = `age(${leftQ} ${rightQ})`;
723
+ } else {
724
+ query = `${leftQ} - ${rightQ}`;
725
+ }
726
+ return trunc ? `date_trunc(${asValue(trunc)}, ${query})` : query;
727
+ },
728
+ }),
922
729
  ),
923
730
 
924
731
  /* pgcrypto funcs */
925
- ...["crypt"].map(
926
- (funcName) =>
927
- ({
928
- name: "$" + funcName,
929
- type: "function",
930
- numArgs: 1,
931
- singleColArg: false,
932
- getFields: (args: any[]) => [args[1]],
933
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
934
- const value = asValue(args[0]) + "",
935
- seedColumnName = asNameAlias(args[1], tableAlias);
936
-
937
- return `crypt(${value}, ${seedColumnName}::text)`;
938
- },
939
- }) as FunctionSpec,
940
- ),
941
-
942
- /* Text col and value funcs */
943
- ...["position", "position_lower"].map(
944
- (funcName) =>
945
- ({
946
- name: "$" + funcName,
947
- type: "function",
948
- numArgs: 1,
949
- singleColArg: false,
950
- getFields: (args: any[]) => [args[1]],
951
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
952
- let a1 = asValue(args[0]),
953
- a2 = asNameAlias(args[1], tableAlias);
954
- if (funcName === "position_lower") {
955
- a1 = `LOWER(${a1}::text)`;
956
- a2 = `LOWER(${a2}::text)`;
957
- }
958
- return `position( ${a1} IN ${a2} )`;
959
- },
960
- }) as FunctionSpec,
961
- ),
962
- ...["template_string"].map(
963
- (funcName) =>
964
- ({
965
- name: "$" + funcName,
966
- type: "function",
967
- numArgs: 1,
968
- minCols: 0,
969
- singleColArg: false,
970
- getFields: (args: any[]) => [] as string[], // Fields not validated because we'll use the allowed ones anyway
971
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
972
- if (typeof args[0] !== "string")
973
- throw "First argument must be a string. E.g.: '{col1} ..text {col2} ...' ";
974
-
975
- const rawValue = args[0];
976
- let finalValue = rawValue;
977
- const usedColumns = allowedFields.filter((fName) => rawValue.includes(`{${fName}}`));
978
- usedColumns.forEach((colName, idx) => {
979
- finalValue = finalValue.split(`{${colName}}`).join(`%${idx + 1}$s`);
980
- });
981
- finalValue = asValue(finalValue);
982
-
983
- if (usedColumns.length) {
984
- return `format(${finalValue}, ${usedColumns.map((c) => `${asNameAlias(c, tableAlias)}::TEXT`).join(", ")})`;
985
- }
732
+ ...["crypt"].map((funcName) =>
733
+ asFunction({
734
+ name: "$" + funcName,
735
+ type: "function",
736
+ numArgs: 1,
737
+ singleColArg: false,
738
+ getFields: (args: any[]) => [args[1]],
739
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
740
+ const value = asValue(args[0]) + "",
741
+ seedColumnName = asNameAlias(args[1], tableAlias);
986
742
 
987
- return `format(${finalValue})`;
988
- },
989
- }) as FunctionSpec,
743
+ return `crypt(${value}, ${seedColumnName}::text)`;
744
+ },
745
+ }),
990
746
  ),
991
747
 
992
- /** Custom highlight -> myterm => ['some text and', ['myterm'], ' and some other text']
993
- * (fields: "*" | string[], term: string, { edgeTruncate: number = -1; noFields: boolean = false }) => string | (string | [string])[]
994
- * edgeTruncate = maximum extra characters left and right of matches
995
- * noFields = exclude field names in search
996
- * */
997
- {
998
- name: "$term_highlight" /* */,
999
- description: ` :[column_names<string[] | "*">, search_term<string>, opts?<{ returnIndex?: number; edgeTruncate?: number; noFields?: boolean }>] -> get case-insensitive text match highlight`,
1000
- type: "function",
1001
- numArgs: 1,
1002
- singleColArg: true,
1003
- canBeUsedForFilter: true,
1004
- getFields: (args: any[]) => args[0],
1005
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias, allColumns }) => {
1006
- const cols = parseFieldFilter(args[0], false, allowedFields);
1007
- let term = args[1];
1008
- const rawTerm = args[1];
1009
- const { edgeTruncate, noFields = false, returnType, matchCase = false } = args[2] || {};
1010
- if (!isEmpty(args[2])) {
1011
- const keys = Object.keys(args[2]);
1012
- const validKeys = ["edgeTruncate", "noFields", "returnType", "matchCase"];
1013
- const bad_keys = keys.filter((k) => !validKeys.includes(k));
1014
- if (bad_keys.length)
1015
- throw (
1016
- "Invalid options provided for $term_highlight. Expecting one of: " +
1017
- validKeys.join(", ")
1018
- );
1019
- }
1020
- if (!cols.length) throw "Cols are empty/invalid";
1021
- if (typeof term !== "string") throw "Non string term provided: " + term;
1022
- if (edgeTruncate !== undefined && (!Number.isInteger(edgeTruncate) || edgeTruncate < -1))
1023
- throw "Invalid edgeTruncate. expecting a positive integer";
1024
- if (typeof noFields !== "boolean") throw "Invalid noFields. expecting boolean";
1025
- const RETURN_TYPES = ["index", "boolean", "object"];
1026
- if (returnType && !RETURN_TYPES.includes(returnType)) {
1027
- throw `returnType can only be one of: ${RETURN_TYPES}`;
1028
- }
1029
-
1030
- const makeTextMatcherArray = (rawText: string, _term: string) => {
1031
- let matchText = rawText,
1032
- term = _term;
1033
- if (!matchCase) {
1034
- matchText = `LOWER(${rawText})`;
1035
- term = `LOWER(${term})`;
1036
- }
1037
- let leftStr = `substr(${rawText}, 1, position(${term} IN ${matchText}) - 1 )`,
1038
- rightStr = `substr(${rawText}, position(${term} IN ${matchText}) + length(${term}) )`;
1039
- if (edgeTruncate) {
1040
- leftStr = `RIGHT(${leftStr}, ${asValue(edgeTruncate)})`;
1041
- rightStr = `LEFT(${rightStr}, ${asValue(edgeTruncate)})`;
1042
- }
1043
- return `
1044
- CASE WHEN position(${term} IN ${matchText}) > 0 AND ${term} <> ''
1045
- THEN array_to_json(ARRAY[
1046
- to_json( ${leftStr}::TEXT ),
1047
- array_to_json(
1048
- ARRAY[substr(${rawText}, position(${term} IN ${matchText}), length(${term}) )::TEXT ]
1049
- ),
1050
- to_json(${rightStr}::TEXT )
1051
- ])
1052
- ELSE
1053
- array_to_json(ARRAY[(${rawText})::TEXT])
1054
- END
1055
- `;
1056
- };
1057
-
1058
- const colRaw =
1059
- "( " +
1060
- cols
1061
- .map(
1062
- (c) =>
1063
- `${noFields ? "" : asValue(c + ": ") + " || "} COALESCE(${asNameAlias(c, tableAlias)}::TEXT, '')`,
1064
- )
1065
- .join(" || ', ' || ") +
1066
- " )";
1067
- let col = colRaw;
1068
- term = asValue(term);
1069
- if (!matchCase) {
1070
- col = "LOWER" + col;
1071
- term = `LOWER(${term})`;
1072
- }
1073
-
1074
- let leftStr = `substr(${colRaw}, 1, position(${term} IN ${col}) - 1 )`,
1075
- rightStr = `substr(${colRaw}, position(${term} IN ${col}) + length(${term}) )`;
1076
- if (edgeTruncate) {
1077
- leftStr = `RIGHT(${leftStr}, ${asValue(edgeTruncate)})`;
1078
- rightStr = `LEFT(${rightStr}, ${asValue(edgeTruncate)})`;
1079
- }
1080
-
1081
- // console.log(col);
1082
- let res = "";
1083
- if (returnType === "index") {
1084
- res = `CASE WHEN position(${term} IN ${col}) > 0 THEN position(${term} IN ${col}) - 1 ELSE -1 END`;
1085
-
1086
- // } else if(returnType === "boolean"){
1087
- // res = `CASE WHEN position(${term} IN ${col}) > 0 THEN TRUE ELSE FALSE END`;
1088
- } else if (returnType === "object" || returnType === "boolean") {
1089
- const hasChars = Boolean(rawTerm && /[a-z]/i.test(rawTerm));
1090
- const validCols = cols
1091
- .map((c) => {
1092
- const colInfo = allColumns.find((ac) => ac.name === c);
1093
- return {
1094
- key: c,
1095
- colInfo,
1096
- };
1097
- })
1098
- .filter((c) => c.colInfo && c.colInfo.udt_name !== "bytea");
1099
-
1100
- const _cols = validCols.filter(
1101
- (c) =>
1102
- /** Exclude numeric columns when the search tern contains a character */
1103
- !hasChars || postgresToTsType(c.colInfo!.udt_name) !== "number",
1104
- );
1105
-
1106
- /** This will break GROUP BY (non-integer constant in GROUP BY) */
1107
- if (!_cols.length) {
1108
- if (validCols.length && hasChars)
1109
- throw `You're searching the impossible: characters in numeric fields. Use this to prevent making such a request in future: /[a-z]/i.test(your_term) `;
1110
- return returnType === "boolean" ? "FALSE" : "NULL";
1111
- }
1112
- res = `CASE
1113
- ${_cols
1114
- .map((c) => {
1115
- const colNameEscaped = asNameAlias(c.key, tableAlias);
1116
- let colSelect = `${colNameEscaped}::TEXT`;
1117
- const isTstamp = c.colInfo?.udt_name.startsWith("timestamp");
1118
- if (isTstamp || c.colInfo?.udt_name === "date") {
1119
- colSelect = `( CASE WHEN ${colNameEscaped} IS NULL THEN ''
1120
- ELSE concat_ws(' ',
1121
- trim(to_char(${colNameEscaped}, 'YYYY-MM-DD HH24:MI:SS')),
1122
- trim(to_char(${colNameEscaped}, 'Day Month')),
1123
- 'Q' || trim(to_char(${colNameEscaped}, 'Q')),
1124
- 'WK' || trim(to_char(${colNameEscaped}, 'WW'))
1125
- ) END)`;
1126
- }
1127
- const colTxt = `COALESCE(${colSelect}, '')`; // position(${term} IN ${colTxt}) > 0
1128
- if (returnType === "boolean") {
1129
- return `
1130
- WHEN ${colTxt} ${matchCase ? "LIKE" : "ILIKE"} ${asValue("%" + rawTerm + "%")}
1131
- THEN TRUE
1132
- `;
1133
- }
1134
- return `
1135
- WHEN ${colTxt} ${matchCase ? "LIKE" : "ILIKE"} ${asValue("%" + rawTerm + "%")}
1136
- THEN json_build_object(
1137
- ${asValue(c.key)},
1138
- ${makeTextMatcherArray(colTxt, term)}
1139
- )::jsonb
1140
- `;
1141
- })
1142
- .join(" ")}
1143
- ELSE ${returnType === "boolean" ? "FALSE" : "NULL"}
1144
-
1145
- END`;
1146
-
1147
- // console.log(res)
1148
- } else {
1149
- /* If no match or empty search THEN return full row as string within first array element */
1150
- res = `CASE WHEN position(${term} IN ${col}) > 0 AND ${term} <> '' THEN array_to_json(ARRAY[
1151
- to_json( ${leftStr}::TEXT ),
1152
- array_to_json(
1153
- ARRAY[substr(${colRaw}, position(${term} IN ${col}), length(${term}) )::TEXT ]
1154
- ),
1155
- to_json(${rightStr}::TEXT )
1156
- ]) ELSE array_to_json(ARRAY[(${colRaw})::TEXT]) END`;
1157
- }
1158
-
1159
- return res;
1160
- },
1161
- },
1162
-
1163
748
  /* Aggs */
1164
749
  ...["max", "min", "count", "avg", "json_agg", "jsonb_agg", "string_agg", "array_agg", "sum"].map(
1165
750
  (aggName) =>
1166
- ({
751
+ asFunction({
1167
752
  name: "$" + aggName,
1168
753
  type: "aggregation",
1169
754
  numArgs: 1,
@@ -1176,10 +761,10 @@ export const FUNCTIONS: FunctionSpec[] = [
1176
761
  }
1177
762
  return aggName + "(" + asNameAlias(args[0], tableAlias) + `${extraArgs})`;
1178
763
  },
1179
- }) satisfies FunctionSpec,
764
+ }),
1180
765
  ),
1181
766
 
1182
- {
767
+ asFunction({
1183
768
  name: "$jsonb_build_object",
1184
769
  type: "function",
1185
770
  numArgs: 22,
@@ -1189,10 +774,10 @@ export const FUNCTIONS: FunctionSpec[] = [
1189
774
  getQuery: ({ args, tableAliasRaw: tableAlias }) => {
1190
775
  return `jsonb_build_object(${args.flatMap((arg) => [asValue(arg), asNameAlias(arg, tableAlias)]).join(", ")})`;
1191
776
  },
1192
- },
777
+ }),
1193
778
 
1194
779
  /* More aggs */
1195
- {
780
+ asFunction({
1196
781
  name: "$countAll",
1197
782
  type: "aggregation",
1198
783
  description: `agg :[] COUNT of all rows `,
@@ -1202,8 +787,8 @@ export const FUNCTIONS: FunctionSpec[] = [
1202
787
  getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
1203
788
  return "COUNT(*)";
1204
789
  },
1205
- } as FunctionSpec,
1206
- {
790
+ }),
791
+ asFunction({
1207
792
  name: "$diff_perc",
1208
793
  type: "aggregation",
1209
794
  numArgs: 1,
@@ -1213,7 +798,7 @@ export const FUNCTIONS: FunctionSpec[] = [
1213
798
  const col = asNameAlias(args[0], tableAlias);
1214
799
  return `round( ( ( MAX(${col}) - MIN(${col}) )::float/MIN(${col}) ) * 100, 2)`;
1215
800
  },
1216
- } as FunctionSpec,
801
+ }),
1217
802
  ];
1218
803
 
1219
804
  /*