goby-database 2.2.29 → 2.2.30

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/index.js CHANGED
@@ -14,7 +14,8 @@ export default class Project {
14
14
  else {
15
15
  console.log('opened goby database');
16
16
  }
17
- //prepared statements with arguments so my code isn't as verbose elsewhere
17
+ // prepared statements with arguments so my code isn't as verbose elsewhere
18
+ // obviously can only do this for actions on determinate columns
18
19
  this.run = {
19
20
  begin: this.db.prepare('BEGIN IMMEDIATE'),
20
21
  commit: this.db.prepare('COMMIT'),
@@ -803,27 +804,29 @@ export default class Project {
803
804
  }
804
805
  // MARKER: modify item retrieval
805
806
  retrieve_class_items({ class_id, class_name, class_data, pagination = {} }) {
807
+ // 1. DETERMINE COLUMNS AND ROWS TO RETRIEVE ---------------------------------------------------
806
808
  var _a, _b, _c, _d, _e;
807
809
  const pagination_defaults = {
808
810
  page_size: null,
809
811
  property_range: 'all',
810
- item_range: 'all'
812
+ item_range: 'all',
813
+ conditions: []
811
814
  };
815
+ // set pagination rules by overriding defaults with any custom settings
812
816
  pagination = Object.assign(Object.assign({}, pagination_defaults), pagination);
817
+ // get class data+name if not already passed in
813
818
  if (class_name == undefined || class_data == undefined) {
814
819
  class_data = this.lookup_class(class_id);
815
820
  class_name = class_data.name;
816
821
  }
817
822
  ;
818
- const class_string = `[class_${class_name}]`;
819
- // joined+added at beginning of the query, built from relations
820
- const cte_strings = [];
821
- // joined+added near the end of the query, built from relations
822
- const cte_joins = [];
823
- // joined+added between SELECT and FROM, built from relations
824
- const relation_selections = [];
823
+ // gets the label prop for this class
825
824
  const label_prop_ids = (_b = (_a = class_data.metadata.label) === null || _a === void 0 ? void 0 : _a.properties) !== null && _b !== void 0 ? _b : [];
826
- // if a property_range is defined, first filter class_data.properties by those IDs
825
+ const where_conditions = [];
826
+ if (pagination.item_range && pagination.item_range !== 'all') {
827
+ where_conditions.push(`system_id in (${pagination.item_range.join(',')})`);
828
+ }
829
+ // if a property_range is defined, first filter properties retrieved by those IDs
827
830
  const retrieved_properties = class_data.properties.filter((prop) => {
828
831
  if (pagination.property_range == 'all' || !pagination.property_range) {
829
832
  return true;
@@ -838,22 +841,51 @@ export default class Project {
838
841
  return true;
839
842
  }
840
843
  });
844
+ const cte_properties = [];
845
+ for (let condition of (pagination.conditions || [])) {
846
+ if (condition.name == 'under_property_max') {
847
+ const property = class_data.properties.find((p) => p.id == condition.property_id);
848
+ if ((property === null || property === void 0 ? void 0 : property.type) == 'relation' && property.max_values !== null) {
849
+ if (!cte_properties.some((p) => p.id == condition.property_id) && !retrieved_properties.some((p) => p.id == condition.property_id)) {
850
+ // if this property isn’t retrieved directly, we have to make sure a CTE is created for it, just so we can count the values
851
+ cte_properties.push(Object.assign(Object.assign({}, property), { cte_only: true }));
852
+ }
853
+ // add a condition that counts the items selected by this item for this property
854
+ where_conditions.push(`COALESCE([count_user_${property.name}],0) < ${property.max_values}`);
855
+ }
856
+ }
857
+ }
858
+ // separates these, since they are handled separately in the query
841
859
  const relation_properties = retrieved_properties.filter(a => a.type == 'relation');
842
860
  const data_properties = retrieved_properties.filter(a => a.type == 'data');
843
- for (let prop of relation_properties) {
861
+ // 2. GENERATE SQLITE QUERY --------------------------------------------------
862
+ // class table name in db
863
+ const class_string = `[class_${class_name}]`;
864
+ // 2a. Handle relation properties by generating common table expressions (ctes)
865
+ // joined+added at beginning of the query, built from relations
866
+ const cte_strings = [];
867
+ // joined+added near the end of the query, built from relations
868
+ const cte_joins = [];
869
+ // joined+added between SELECT and FROM, built from relations
870
+ const relation_selections = [];
871
+ for (let prop of [...relation_properties, ...cte_properties]) {
844
872
  const target_selects = [];
845
- let property_junction_column_name = junction_col_name(class_id, prop.id);
873
+ // name of column for this class/property in junction table
874
+ const property_junction_column_name = junction_col_name(class_id, prop.id);
875
+ // loop through each target
846
876
  if (prop.relation_targets.length > 0) {
847
877
  for (let i = 0; i < prop.relation_targets.length; i++) {
848
878
  // find the side that does not match both the class and prop IDs
849
- let target = prop.relation_targets[i];
879
+ const target = prop.relation_targets[i];
850
880
  const target_class = this.class_cache.find((a) => a.id == (target === null || target === void 0 ? void 0 : target.class_id));
851
881
  if (target && target_class) {
882
+ // target column name in junction table
852
883
  let target_junction_column_name = junction_col_name(target.class_id, target.prop_id);
853
- // NOTE: as mentioned elsewhere, possibly allow multiple label props
884
+ // get label of label property in target prop
854
885
  const target_label_id = (_d = (_c = target_class === null || target_class === void 0 ? void 0 : target_class.metadata) === null || _c === void 0 ? void 0 : _c.label) === null || _d === void 0 ? void 0 : _d.properties[0];
855
886
  const target_label = target_class === null || target_class === void 0 ? void 0 : target_class.properties.find((p) => p.id == target_label_id);
856
887
  const label_sql_string = target_label ? `,'user_${target_label.name}',target_class."user_${target_label.name}"` : '';
888
+ // NOTE: as mentioned elsewhere, possibly allow multiple label props
857
889
  let junction_id = target.junction_id;
858
890
  let target_select = `
859
891
  SELECT
@@ -870,7 +902,7 @@ export default class Project {
870
902
  }
871
903
  // uses built-in aggregate json function instead of group_concat craziness
872
904
  const cte = `[${prop.id}_cte] AS (
873
- SELECT "${property_junction_column_name}", json_group_array( json(target_data) ) AS [user_${prop.name}]
905
+ SELECT "${property_junction_column_name}", json_group_array( json(target_data) ) AS [user_${prop.name}], COUNT(1) AS [count_user_${prop.name}]
874
906
  FROM
875
907
  (
876
908
  ${target_selects.join(`
@@ -882,28 +914,27 @@ export default class Project {
882
914
 
883
915
  )`;
884
916
  cte_strings.push(cte);
885
- relation_selections.push(`[${prop.id}_cte].[user_${prop.name}]`);
917
+ if (!("cte_only" in prop && prop.cte_only))
918
+ relation_selections.push(`[${prop.id}_cte].[user_${prop.name}]`);
886
919
  cte_joins.push(`LEFT JOIN [${prop.id}_cte] ON [${prop.id}_cte]."${property_junction_column_name}" = ${class_string}.system_id`);
887
920
  }
888
921
  else {
889
- relation_selections.push(`'[]' AS [user_${prop.name}]`);
922
+ if (!("cte_only" in prop && prop.cte_only))
923
+ relation_selections.push(`'[]' AS [user_${prop.name}]`);
890
924
  }
891
925
  }
892
926
  let orderby = `ORDER BY ${class_string}.system_order`;
893
927
  const data_prop_sql_string = data_properties.length > 0 ? ', ' + data_properties.map((p) => `[user_${p.name}]`).join(',') : '';
894
928
  const table_selection = pagination.property_range == 'all' ? `[class_${class_name}].*` : `system_id,system_order${data_prop_sql_string}`;
895
- let filter_by_items = '';
896
- if (pagination.item_range && pagination.item_range !== 'all') {
897
- filter_by_items = `WHERE system_id in (${pagination.item_range.join(',')})`;
898
- }
899
- let comma_break = `,
929
+ const where_string = where_conditions.length > 0 ? `WHERE ${where_conditions.map((w) => `(${w})`).join(' AND ')}` : '';
930
+ const comma_break = `,
900
931
  `;
901
932
  let query = `
902
933
  ${cte_strings.length > 0 ? "WITH " + cte_strings.join(comma_break) : ''}
903
934
  SELECT ${table_selection} ${relation_selections.length > 0 ? ', ' + relation_selections.join(`, `) : ''}
904
935
  FROM [class_${class_name}]
905
936
  ${cte_joins.join(' ')}
906
- ${filter_by_items}
937
+ ${where_string}
907
938
  ${orderby}`;
908
939
  // possibly elaborate this any type a little more in the future, e.g. a CellValue or SQLCellValue type that expects some wildcards
909
940
  let items = this.db.prepare(query).all();
package/dist/types.d.ts CHANGED
@@ -142,6 +142,10 @@ export type ClassData = {
142
142
  properties: Property[];
143
143
  };
144
144
  export type ClassList = ClassData[];
145
+ type ConditionUnderPropertyMax = {
146
+ name: 'under_property_max';
147
+ property_id: number;
148
+ };
145
149
  export type ItemPagination = {
146
150
  page_size?: number | null;
147
151
  page_range?: [start: number, end?: number];
@@ -149,6 +153,7 @@ export type ItemPagination = {
149
153
  property_range?: number[] | 'slim' | 'all';
150
154
  /** Filter by item IDs. If not specified, pulls all */
151
155
  item_range?: number[] | 'all';
156
+ conditions?: ConditionUnderPropertyMax[];
152
157
  };
153
158
  export type PaginatedItems = ItemPagination & {
154
159
  loaded: ClassRow[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goby-database",
3
- "version": "2.2.29",
3
+ "version": "2.2.30",
4
4
  "description": "This will hold the core better-sqlite3-powered application for creating and modifying goby databases",
5
5
  "main": "dist/index.js",
6
6
  "files": [