• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ngageoint / geopackage-js / 4026249674

pending completion
4026249674

push

github

Christopher Caldwell
update typescript, fix linter errors and remove module comments

3590 of 8008 branches covered (44.83%)

Branch coverage included in aggregate %.

1218 of 1218 new or added lines in 92 files covered. (100.0%)

15084 of 20419 relevant lines covered (73.87%)

1570.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.91
/lib/db/sqlUtils.ts
1
/**
2
 * Core SQL Utility methods
3
 */
4
import { StringUtils } from './stringUtils';
1✔
5
import { UserColumn } from '../user/userColumn';
6
import { UserTable } from '../user/userTable';
7
import { TableMapping } from './tableMapping';
1✔
8
import { TableInfo } from './table/tableInfo';
1✔
9
import { SQLiteMaster } from './master/sqliteMaster';
1✔
10
import { SQLiteMasterQuery } from './master/sqliteMasterQuery';
1✔
11
import { SQLiteMasterColumn } from './master/sqliteMasterColumn';
1✔
12
import { GeoPackageDataType } from './geoPackageDataType';
1✔
13
import { ResultSet } from './resultSet';
14
import { ResultSetResult } from './resultSetResult';
1✔
15
import { ResultUtils } from './resultUtils';
1✔
16
import { ContentValues } from '../user/contentValues';
17
import { DBAdapter } from './dbAdapter';
18
import type { GeoPackageConnection } from './geoPackageConnection';
19
import { DBValue } from './dbValue';
20

21
export class SQLUtils {
1✔
22
  /**
23
   * Pattern for matching numbers
24
   */
25
  static NUMBER_PATTERN = '\\d+';
1✔
26

27
  static quoteWrap(str: string): string {
1✔
28
    return StringUtils.quoteWrap(str);
38,146✔
29
  }
30

31
  /**
32
   * Create the user defined table SQL
33
   *
34
   * @param table user table
35
   * @param <TColumn> column type
36
   * @return create table SQL
37
   */
38
  static createTableSQL(table: UserTable<UserColumn>): string {
1✔
39
    // Build the create table sql
40
    let sql = '';
321✔
41
    sql = sql.concat('CREATE TABLE ').concat(StringUtils.quoteWrap(table.getTableName())).concat(' (');
321✔
42

43
    // Add each column to the sql
44
    const columns = table.getUserColumns().getColumns();
321✔
45
    for (let i = 0; i < columns.length; i++) {
321✔
46
      const column = columns[i];
1,768✔
47
      if (i > 0) {
1,768✔
48
        sql = sql.concat(',');
1,447✔
49
      }
50
      sql = sql.concat('\n  ');
1,768✔
51
      sql = sql.concat(SQLUtils.columnSQL(column));
1,768✔
52
    }
53

54
    // Add unique constraints
55
    table
321✔
56
      .getConstraints()
57
      .all()
58
      .forEach((constraint) => {
59
        sql = sql.concat(',\n  ');
40✔
60
        sql = sql.concat(constraint.buildSql());
40✔
61
      });
62

63
    sql = sql.concat('\n);');
321✔
64
    return sql;
321✔
65
  }
66

67
  /**
68
   * Create the column SQL in the format:
69
   * "column_name" column_type[(max)] [NOT NULL] [PRIMARY KEY AUTOINCREMENT]
70
   * @param column user column
71
   * @return column SQL
72
   */
73
  static columnSQL(column: UserColumn): string {
1✔
74
    return StringUtils.quoteWrap(column.getName()) + ' ' + SQLUtils.columnDefinition(column);
1,768✔
75
  }
76

77
  /**
78
   * Create the column definition SQL in the format:
79
   * column_type[(max)] [NOT NULL] [PRIMARY KEY AUTOINCREMENT]
80
   * @param column  user column
81
   * @return column definition SQL
82
   */
83
  static columnDefinition(column: UserColumn): string {
1✔
84
    let sql = '';
1,772✔
85

86
    sql = sql.concat(column.getType());
1,772✔
87
    if (column.hasMax()) {
1,772✔
88
      sql = sql.concat('(').concat(column.getMax().toString()).concat(')');
28✔
89
    }
90

91
    column
1,772✔
92
      .getConstraints()
93
      .all()
94
      .forEach((constraint) => {
95
        sql = sql.concat(' ');
1,362✔
96
        sql = sql.concat(column.buildConstraintSql(constraint));
1,362✔
97
      });
98

99
    return sql.toString();
1,772✔
100
  }
101

102
  /**
103
   * Query for the foreign keys value
104
   *
105
   * @param db
106
   *            connection
107
   * @return true if enabled, false if disabled
108
   */
109
  static foreignKeys(db: GeoPackageConnection): boolean {
1✔
110
    const foreignKeys = db.get('PRAGMA foreign_keys', null)[0] as boolean;
23✔
111
    return foreignKeys !== null && foreignKeys !== undefined && foreignKeys;
23!
112
  }
113

114
  /**
115
   * Change the foreign keys state
116
   * @param db connection
117
   * @param on true to turn on, false to turn off
118
   * @return previous foreign keys value
119
   */
120
  static setForeignKeys(db: GeoPackageConnection, on: boolean): boolean {
1✔
121
    const foreignKeys = SQLUtils.foreignKeys(db);
23✔
122
    if (foreignKeys !== on) {
23!
123
      const sql = SQLUtils.foreignKeysSQL(on);
×
124
      db.run(sql);
×
125
    }
126
    return foreignKeys;
23✔
127
  }
128

129
  /**
130
   * Create the foreign keys SQL
131
   * @param on true to turn on, false to turn off
132
   * @return foreign keys SQL
133
   */
134
  static foreignKeysSQL(on: boolean): string {
1✔
135
    return 'PRAGMA foreign_keys = ' + on;
×
136
  }
137

138
  /**
139
   * Perform a foreign key check
140
   * @param db connection
141
   * @return empty list if valid or violation errors, 4 column values for each violation. see SQLite PRAGMA foreign_key_check
142
   */
143
  static foreignKeyCheck(db: GeoPackageConnection): any[] {
1✔
144
    const sql = SQLUtils.foreignKeyCheckSQL(null);
×
145
    return db.all(sql, null);
×
146
  }
147

148
  /**
149
   * Perform a foreign key check
150
   * @param db connection
151
   * @param tableName table name
152
   * @return empty list if valid or violation errors, 4 column values for each violation. see SQLite PRAGMA foreign_key_check
153
   */
154
  static foreignKeyCheckForTable(db: GeoPackageConnection, tableName: string): any[] {
1✔
155
    const sql = SQLUtils.foreignKeyCheckSQL(tableName);
×
156
    return db.all(sql, null);
×
157
  }
158

159
  /**
160
   * Create the foreign key check SQL
161
   * @param tableName table name
162
   * @return foreign key check SQL
163
   */
164
  static foreignKeyCheckSQL(tableName: string): string {
1✔
165
    return (
18✔
166
      'PRAGMA foreign_key_check' +
167
      (tableName !== null && tableName !== undefined ? '(' + StringUtils.quoteWrap(tableName) + ')' : '')
54!
168
    );
169
  }
170

171
  /**
172
   * Create the integrity check SQL
173
   * @return integrity check SQL
174
   */
175
  static integrityCheckSQL(): string {
1✔
176
    return 'PRAGMA integrity_check';
18✔
177
  }
178

179
  /**
180
   * Create the quick check SQL
181
   * @return quick check SQL
182
   */
183
  static quickCheckSQL(): string {
1✔
184
    return 'PRAGMA quick_check';
18✔
185
  }
186

187
  /**
188
   * Drop the table if it exists
189
   * @param db connection
190
   * @param tableName table name
191
   */
192
  static dropTable(db: GeoPackageConnection, tableName: string): void {
1✔
193
    const sql = SQLUtils.dropTableSQL(tableName);
229✔
194
    db.run(sql);
229✔
195
  }
196

197
  /**
198
   * Create the drop table if exists SQL
199
   * @param tableName table name
200
   * @return drop table SQL
201
   */
202
  static dropTableSQL(tableName: string): string {
1✔
203
    return 'DROP TABLE IF EXISTS ' + StringUtils.quoteWrap(tableName);
229✔
204
  }
205

206
  /**
207
   * Drop the view if it exists
208
   * @param db connection
209
   * @param viewName view name
210
   */
211
  static dropView(db: GeoPackageConnection, viewName: string): void {
1✔
212
    const sql = SQLUtils.dropViewSQL(viewName);
×
213
    db.run(sql);
×
214
  }
215

216
  /**
217
   * Create the drop view if exists SQL
218
   * @param viewName view name
219
   * @return drop view SQL
220
   */
221
  static dropViewSQL(viewName: string): string {
1✔
222
    return 'DROP VIEW IF EXISTS ' + StringUtils.quoteWrap(viewName);
×
223
  }
224

225
  /**
226
   * Transfer table content from one table to another
227
   * @param db connection
228
   * @param tableMapping table mapping
229
   */
230
  static transferTableContentForTableMapping(db: GeoPackageConnection, tableMapping: TableMapping): void {
1✔
231
    const sql = SQLUtils.transferTableContentSQL(tableMapping);
39✔
232
    db.run(sql);
39✔
233
  }
234

235
  /**
236
   * Create insert SQL to transfer table content from one table to another
237
   * @param tableMapping table mapping
238
   * @return transfer SQL
239
   */
240
  static transferTableContentSQL(tableMapping: TableMapping): string {
1✔
241
    let insert = 'INSERT INTO ';
39✔
242
    insert = insert.concat(StringUtils.quoteWrap(tableMapping.toTable));
39✔
243
    insert = insert.concat(' (');
39✔
244
    let selectColumns = '';
39✔
245
    let where = '';
39✔
246
    if (tableMapping.hasWhere()) {
39!
247
      where = where.concat(tableMapping.where);
×
248
    }
249
    const columns = tableMapping.getColumns();
39✔
250
    tableMapping.getColumnNames().forEach((key: string) => {
39✔
251
      const toColumn = key;
203✔
252
      const column = columns[key];
203✔
253
      if (selectColumns.length > 0) {
203✔
254
        insert = insert.concat(', ');
164✔
255
        selectColumns = selectColumns.concat(', ');
164✔
256
      }
257
      insert = insert.concat(StringUtils.quoteWrap(toColumn));
203✔
258

259
      if (column.hasConstantValue()) {
203✔
260
        selectColumns = selectColumns.concat(column.getConstantValueAsString());
28✔
261
      } else {
262
        if (column.hasDefaultValue()) {
175✔
263
          selectColumns = selectColumns.concat('ifnull(');
57✔
264
        }
265
        selectColumns = selectColumns.concat(StringUtils.quoteWrap(column.fromColumn));
175✔
266
        if (column.hasDefaultValue()) {
175✔
267
          selectColumns = selectColumns.concat(',');
57✔
268
          selectColumns = selectColumns.concat(column.getDefaultValueAsString());
57✔
269
          selectColumns = selectColumns.concat(')');
57✔
270
        }
271
      }
272

273
      if (column.hasWhereValue()) {
203✔
274
        if (where.length > 0) {
32✔
275
          where = where.concat(' AND ');
11✔
276
        }
277
        where = where.concat(StringUtils.quoteWrap(column.fromColumn));
32✔
278
        where = where.concat(' ');
32✔
279
        where = where.concat(column.whereOperator);
32✔
280
        where = where.concat(' ');
32✔
281
        where = where.concat(column.getWhereValueAsString());
32✔
282
      }
283
    });
284

285
    insert = insert.concat(') SELECT ');
39✔
286
    insert = insert.concat(selectColumns);
39✔
287
    insert = insert.concat(' FROM ');
39✔
288
    insert = insert.concat(StringUtils.quoteWrap(tableMapping.fromTable));
39✔
289

290
    if (where.length > 0) {
39✔
291
      insert = insert.concat(' WHERE ');
21✔
292
      insert = insert.concat(where);
21✔
293
    }
294

295
    return insert.toString();
39✔
296
  }
297

298
  /**
299
   * Transfer table content to itself with new rows containing a new column
300
   * value. All rows containing the current column value are inserted as new
301
   * rows with the new column value.
302
   * @param db connection
303
   * @param tableName table name
304
   * @param columnName column name
305
   * @param newColumnValue new column value for new rows
306
   * @param currentColumnValue column value for rows to insert as new rows
307
   * @param idColumnName id column name
308
   */
309
  static transferTableContent(
1✔
310
    db: GeoPackageConnection,
311
    tableName: string,
312
    columnName: string,
313
    newColumnValue: any,
314
    currentColumnValue: any,
315
    idColumnName?: string,
316
  ): void {
317
    const tableInfo = TableInfo.info(db, tableName);
6✔
318
    const tableMapping = TableMapping.fromTableInfo(tableInfo);
6✔
319
    if (idColumnName != null) {
6!
320
      tableMapping.removeColumn(idColumnName);
×
321
    }
322
    const column = tableMapping.getColumn(columnName);
6✔
323
    column.constantValue = newColumnValue;
6✔
324
    column.whereValue = currentColumnValue;
6✔
325
    SQLUtils.transferTableContentForTableMapping(db, tableMapping);
6✔
326
  }
327

328
  /**
329
   * Get an available temporary table name. Starts with prefix_baseName and
330
   * then continues with prefix#_baseName starting at 1 and increasing.
331
   * @param db connection
332
   * @param prefix name prefix
333
   * @param baseName base name
334
   * @return unused table name
335
   */
336
  static tempTableName(db: GeoPackageConnection, prefix: string, baseName: string): string {
1✔
337
    let name = prefix + '_' + baseName;
4✔
338
    let nameNumber = 0;
4✔
339
    while (db.tableExists(name)) {
4✔
340
      name = prefix + ++nameNumber + '_' + baseName;
×
341
    }
342
    return name;
4✔
343
  }
344

345
  /**
346
   * Modify the SQL with a name change and the table mapping modifications
347
   * @param db optional connection, used for SQLite Master name conflict detection
348
   * @param name statement name
349
   * @param sql SQL statement
350
   * @param tableMapping table mapping
351
   * @return updated SQL, null if SQL contains a deleted column
352
   */
353
  static modifySQL(db: GeoPackageConnection, name: string, sql: string, tableMapping: TableMapping): string {
1✔
354
    let updatedSql = sql;
145✔
355
    if (name !== null && name !== undefined && tableMapping.isNewTable()) {
145!
356
      const newName = SQLUtils.createName(db, name, tableMapping.fromTable, tableMapping.toTable);
×
357
      const updatedName = SQLUtils.replaceName(updatedSql, name, newName);
×
358
      if (updatedName !== null && updatedName !== undefined) {
×
359
        updatedSql = updatedName;
×
360
      }
361
      const updatedTable = SQLUtils.replaceName(updatedSql, tableMapping.fromTable, tableMapping.toTable);
×
362
      if (updatedTable !== null && updatedTable !== undefined) {
×
363
        updatedSql = updatedTable;
×
364
      }
365
    }
366
    updatedSql = SQLUtils.modifySQLWithTableMapping(updatedSql, tableMapping);
145✔
367
    return updatedSql;
145✔
368
  }
369

370
  /**
371
   * Modify the SQL with table mapping modifications
372
   * @param sql SQL statement
373
   * @param tableMapping table mapping
374
   * @return updated SQL, null if SQL contains a deleted column
375
   */
376
  static modifySQLWithTableMapping(sql: string, tableMapping: TableMapping): string {
1✔
377
    let updatedSql = sql;
145✔
378

379
    const droppedColumns = Array.from(tableMapping.droppedColumns);
145✔
380
    for (let i = 0; i < droppedColumns.length; i++) {
145✔
381
      const column = droppedColumns[i];
42✔
382
      const updated = SQLUtils.replaceName(updatedSql, column, ' ');
42✔
383
      if (updated !== null && updated !== undefined) {
42!
384
        updatedSql = null;
×
385
        break;
×
386
      }
387
    }
388

389
    if (updatedSql !== null && updatedSql !== undefined) {
145!
390
      tableMapping.getMappedColumns().forEach((column) => {
145✔
391
        if (column.hasNewName()) {
805!
392
          const updated = SQLUtils.replaceName(updatedSql, column.fromColumn, column.toColumn);
×
393
          if (updated !== null && updated !== undefined) {
×
394
            updatedSql = updated;
×
395
          }
396
        }
397
      });
398
    }
399
    return updatedSql;
145✔
400
  }
401

402
  /**
403
   * Replace the name (table, column, etc) in the SQL with the replacement.
404
   * The name must be surrounded by non word characters (i.e. not a subset of
405
   * another name).
406
   * @param sql SQL statement
407
   * @param name name
408
   * @param replacement replacement value
409
   * @return null if not modified, SQL value if replaced at least once
410
   */
411
  static replaceName(sql: string, name: string, replacement: string): string {
1✔
412
    let updatedSql = null;
42✔
413

414
    // Quick check if contained in the SQL
415
    if (sql.indexOf(name) >= 0) {
42!
416
      let updated = false;
×
417
      let updatedSqlBuilder = '';
×
418

419
      // Split the SQL apart by the name
420
      const parts = sql.split(name);
×
421

422
      for (let i = 0; i <= parts.length; i++) {
×
423
        if (i > 0) {
×
424
          // Find the character before the name
425
          let before = '_';
×
426
          const beforePart = parts[i - 1];
×
427
          if (beforePart.length === 0) {
×
428
            if (i == 1) {
×
429
              // SQL starts with the name, allow
430
              before = ' ';
×
431
            }
432
          } else {
433
            before = beforePart.substring(beforePart.length - 1);
×
434
          }
435

436
          // Find the character after the name
437
          let after = '_';
×
438
          if (i < parts.length) {
×
439
            const afterPart = parts[i];
×
440
            if (afterPart.length !== 0) {
×
441
              after = afterPart.substring(0, 1);
×
442
            }
443
          } else if (sql.endsWith(name)) {
×
444
            // SQL ends with the name, allow
445
            after = ' ';
×
446
          } else {
447
            break;
×
448
          }
449

450
          // Check the before and after characters for non word
451
          // characters
452
          if (before.match('\\W').length > 0 && after.match('\\W').length > 0) {
×
453
            // Replace the name
454
            updatedSqlBuilder = updatedSqlBuilder.concat(replacement);
×
455
            updated = true;
×
456
          } else {
457
            // Preserve the name
458
            updatedSqlBuilder = updatedSqlBuilder.concat(name);
×
459
          }
460
        }
461

462
        // Add the part to the SQL
463
        if (i < parts.length) {
×
464
          updatedSqlBuilder = updatedSqlBuilder.concat(parts[i]);
×
465
        }
466
      }
467
      // Set if the SQL was modified
468
      if (updated) {
×
469
        updatedSql = updatedSqlBuilder.toString();
×
470
      }
471
    }
472
    return updatedSql;
42✔
473
  }
474

475
  /**
476
   * Create a new name by replacing a case insensitive value with a new value.
477
   * If no replacement is done, create a new name in the form name_#, where #
478
   * is either 2 or one greater than an existing name number suffix. When a db
479
   * connection is provided, check for conflicting SQLite Master names and
480
   * increment # until an available name is found.
481
   * @param db optional connection, used for SQLite Master name conflict detection
482
   * @param name current name
483
   * @param replace value to replace
484
   * @param replacement replacement value
485
   * @return new name
486
   */
487
  static createName(db: GeoPackageConnection, name: string, replace: string, replacement: string): string {
1✔
488
    // Attempt the replacement
489
    let newName = name.replace(new RegExp(replace), replacement);
7✔
490

491
    // If no name change was made
492
    if (newName === name) {
7!
493
      let baseName = newName;
×
494
      let count = 1;
×
495
      // Find any existing end number: name_#
496
      const index = baseName.lastIndexOf('_');
×
497
      if (index >= 0 && index + 1 < baseName.length) {
×
498
        const numberPart = baseName.substring(index + 1);
×
499
        if (numberPart.match(SQLUtils.NUMBER_PATTERN).length > 0) {
×
500
          baseName = baseName.substring(0, index);
×
501
          count = parseInt(numberPart);
×
502
        }
503
      }
504
      // Set the new name to name_2 or name_(#+1)
505
      newName = baseName + '_' + ++count;
×
506

507
      if (db !== null && db !== undefined) {
×
508
        // Check for conflicting SQLite Master table names
509
        while (
×
510
          SQLiteMaster.count(db, null, SQLiteMasterQuery.createForColumnValue(SQLiteMasterColumn.NAME, newName)) > 0
511
        ) {
512
          newName = baseName + '_' + ++count;
×
513
        }
514
      }
515
    }
516
    return newName;
7✔
517
  }
518

519
  /**
520
   * Get the column default value as a string
521
   *
522
   * @param defaultValue default value
523
   * @param dataType data type
524
   * @return default value
525
   */
526
  public static columnDefaultValue(defaultValue: any, dataType: GeoPackageDataType): string {
1✔
527
    return GeoPackageDataType.columnDefaultValue(defaultValue, dataType);
×
528
  }
529

530
  /**
531
   * Rebuild the GeoPackage, repacking it into a minimal amount of disk space
532
   * @param db connection
533
   */
534
  static vacuum(db: GeoPackageConnection): void {
1✔
535
    db.run('VACUUM');
×
536
  }
537

538
  /**
539
   * Execute the SQL
540
   *
541
   * @param connection
542
   *            connection
543
   * @param sql
544
   *            sql statement
545
   */
546
  public static execSQL(connection: GeoPackageConnection, sql: string): void {
1✔
547
    connection.connectionSource.run(sql);
×
548
  }
549

550
  /**
551
   * Query for results
552
   * @param connection connection
553
   * @param sql sql statement
554
   * @param selectionArgs selection arguments
555
   * @return result set
556
   */
557
  public static query(
1✔
558
    connection: GeoPackageConnection | DBAdapter,
559
    sql: string,
560
    selectionArgs: [] | Record<string, any>,
561
  ): ResultSet {
562
    return connection.query(sql, selectionArgs);
3,785✔
563
  }
564

565
  /**
566
   * Query for values up to the limit
567
   *
568
   * @param connection connection
569
   * @param sql sql statement
570
   * @param args arguments
571
   * @param limit result row limit
572
   * @return results
573
   */
574
  public static queryResults(
1✔
575
    connection: GeoPackageConnection | DBAdapter,
576
    sql: string,
577
    args: [] | Record<string, any>,
578
    limit: number,
579
  ): Array<Array<any>> {
580
    const result = SQLUtils.wrapQuery(connection, sql, args);
×
581
    const value = ResultUtils.buildResults(result, limit);
×
582
    result.close();
×
583
    return value;
×
584
  }
585

586
  /**
587
   * Count for query
588
   * @param connection connection
589
   * @param tableName table
590
   * @param where where clause
591
   * @param whereArgs where arguments
592
   * @return number the count of rows
593
   */
594
  public static count(
1✔
595
    connection: GeoPackageConnection,
596
    tableName: string,
597
    where: string,
598
    whereArgs: [] | Record<string, any>,
599
  ): number {
600
    return connection.connectionSource.count(tableName, where, whereArgs);
1,069✔
601
  }
602

603
  /**
604
   * Attempt to count the results of the query
605
   * @param connection connection
606
   * @param sql SQL statement
607
   * @param selectionArgs selection arguments
608
   * @return count if known, -1 if not able to determine
609
   */
610
  public static countSqlQuery(connection: GeoPackageConnection | DBAdapter, sql: string, selectionArgs: any[]): number {
1✔
611
    const sqlCommands = [];
1,237✔
612

613
    const upperCaseSQL = sql.toUpperCase();
1,237✔
614
    const afterSelectIndex = upperCaseSQL.indexOf('SELECT') + 'SELECT'.length;
1,237✔
615
    const upperCaseAfterSelect = upperCaseSQL.substring(afterSelectIndex).trim();
1,237✔
616

617
    if (upperCaseAfterSelect.startsWith('COUNT')) {
1,237!
618
      sqlCommands.push(sql);
×
619
    } else {
620
      const fromIndex = upperCaseSQL.indexOf('FROM');
1,237✔
621
      if (upperCaseAfterSelect.startsWith('DISTINCT')) {
1,237!
622
        const commaIndex = upperCaseSQL.indexOf(',');
×
623
        if (commaIndex < 0 || commaIndex >= fromIndex) {
×
624
          sqlCommands.push(
×
625
            SQLUtils.adjustCount(
626
              'SELECT COUNT(' + sql.substring(afterSelectIndex, fromIndex) + ') ' + sql.substring(fromIndex),
627
            ),
628
          );
629

630
          const isNull = ['SELECT COUNT(*) > 0 '];
×
631
          const columnIsNull =
632
            sql.substring(upperCaseSQL.indexOf('DISTINCT') + 'DISTINCT'.length, fromIndex) + 'IS NULL';
×
633
          const whereIndex = upperCaseSQL.indexOf('WHERE');
×
634
          let endIndex = sql.indexOf(';');
×
635
          if (endIndex < 0) {
×
636
            endIndex = sql.length;
×
637
          }
638
          if (whereIndex >= 0) {
×
639
            isNull.push(sql.substring(fromIndex, whereIndex + 'WHERE'.length));
×
640
            isNull.push(columnIsNull);
×
641
            isNull.push(' AND ( ');
×
642
            isNull.push(sql.substring(whereIndex + 'WHERE'.length, endIndex));
×
643
            isNull.push(' )');
×
644
          } else {
645
            isNull.push(sql.substring(fromIndex, endIndex));
×
646
            isNull.push(' WHERE');
×
647
            isNull.push(columnIsNull);
×
648
          }
649
          sqlCommands.push(SQLUtils.adjustCount(isNull.join('')));
×
650
        }
651
      } else if (fromIndex != -1) {
1,237!
652
        sqlCommands.push(SQLUtils.adjustCount('SELECT COUNT(*) ' + sql.substring(fromIndex)));
1,237✔
653
      }
654
    }
655

656
    let count = -1;
1,237✔
657
    if (sqlCommands.length === 0) {
1,237!
658
      // Unable to count
659
      console.info('Unable to count query without result iteration. SQL: ' + sql + ', args: ' + selectionArgs);
×
660
    } else {
661
      count = 0;
1,237✔
662
      for (const sqlCommand of sqlCommands) {
1,237✔
663
        try {
1,237✔
664
          const value = SQLUtils.querySingleResultWithColumnIndex(connection, sqlCommand, selectionArgs, 0);
1,237✔
665
          if (value != null) {
1,237!
666
            count += value as number;
1,237✔
667
          }
668
        } catch (e) {
669
          console.warn('Unable to count query without result iteration. SQL: ' + sql + ', args: ' + selectionArgs);
×
670
          count = -1;
×
671
        }
672
      }
673
    }
674
    return count;
1,237✔
675
  }
676

677
  /**
678
   * Adjust the count statement as needed
679
   * @param sql sql statement
680
   * @return adjusted or original statement
681
   */
682
  private static adjustCount(sql: string): string {
1✔
683
    const upperCase = sql.toUpperCase();
1,237✔
684
    const limitIndex = upperCase.indexOf(' LIMIT ');
1,237✔
685
    if (limitIndex >= 0) {
1,237!
686
      const lastParenthesis = sql.lastIndexOf(')');
×
687
      if (lastParenthesis == -1 || limitIndex > lastParenthesis) {
×
688
        sql = sql.substring(0, limitIndex);
×
689
      }
690
    }
691
    return sql;
1,237✔
692
  }
693

694
  // /**
695
  //  * Create SQL for adding a column
696
  //  * @param db connection
697
  //  * @param tableName table name
698
  //  * @param column user column
699
  //  */
700
  // public static addColumn(db: GeoPackageConnection, tableName: string, column: UserColumn): void {
701
  //   AlterTable.addColumn(db, tableName, column.getName(), SQLUtils.columnDefinition(column));
702
  // }
703

704
  /**
705
   * Perform the query and wrap as a result
706
   * @param connection connection
707
   * @param sql sql statement
708
   * @param selectionArgs selection arguments
709
   * @return result
710
   */
711
  public static wrapQuery(
1✔
712
    connection: GeoPackageConnection | DBAdapter,
713
    sql: string,
714
    selectionArgs: [] | Record<string, any>,
715
  ): ResultSetResult {
716
    return new ResultSetResult(this.query(connection, sql, selectionArgs));
1,267✔
717
  }
718

719
  /**
720
   * Query the SQL for a single result object with the expected data type
721
   * @param connection connection
722
   * @param sql sql statement
723
   * @param args arguments
724
   * @param columnName column name
725
   * @return result, null if no result
726
   */
727
  public static querySingleResult(
1✔
728
    connection: GeoPackageConnection,
729
    sql: string,
730
    args: [] | Record<string, any>,
731
    columnName: string,
732
  ): any {
733
    const result = SQLUtils.wrapQuery(connection, sql, args);
×
734
    const value = ResultUtils.buildSingleResult(result, columnName);
×
735
    result.close();
×
736
    return value;
×
737
  }
738

739
  /**
740
   * Query the SQL for a single result object with the expected data type
741
   * @param connection connection
742
   * @param sql sql statement
743
   * @param args arguments
744
   * @param columnIdx column index
745
   * @return result, null if no result
746
   */
747
  public static querySingleResultWithColumnIndex(
1✔
748
    connection: GeoPackageConnection | DBAdapter,
749
    sql: string,
750
    args: [] | Record<string, any>,
751
    columnIdx = 0,
1,267!
752
  ): any {
753
    const result = SQLUtils.wrapQuery(connection, sql, args);
1,267✔
754
    const value = ResultUtils.buildSingleResultWithColumnIndex(result, columnIdx);
1,267✔
755
    result.close();
1,267✔
756
    return value;
1,267✔
757
  }
758

759
  /**
760
   * Execute a deletion
761
   * @param connection connection
762
   * @param table table name
763
   * @param where where clause
764
   * @param args where arguments
765
   * @return deleted count
766
   */
767
  public static delete(connection: GeoPackageConnection, table: string, where: string, args: []): number {
1✔
768
    return connection.delete(table, where, args);
×
769
  }
770

771
  /**
772
   * Update table rows
773
   * @param connection connection
774
   * @param table table name
775
   * @param values content values
776
   * @param whereClause where clause
777
   * @param whereArgs where arguments
778
   * @return updated count
779
   */
780
  public static update(
1✔
781
    connection: GeoPackageConnection,
782
    table: string,
783
    values: ContentValues,
784
    whereClause: string,
785
    whereArgs: DBValue[],
786
  ): number {
787
    const update = [];
18✔
788
    update.push('update ');
18✔
789
    update.push(SQLUtils.quoteWrap(table));
18✔
790
    update.push(' set ');
18✔
791
    const setValuesSize = values.size();
18✔
792
    const argsSize = whereArgs == null ? setValuesSize : setValuesSize + whereArgs.length;
18✔
793
    const args = [];
18✔
794
    let i = 0;
18✔
795
    for (const colName of values.keySet()) {
102✔
796
      update.push(i > 0 ? ',' : '');
102✔
797
      update.push(SQLUtils.quoteWrap(colName));
102✔
798
      args.push(values.get(colName));
102✔
799
      update.push(' = ?');
102✔
800
      i++;
102✔
801
    }
802
    if (whereArgs != null) {
18✔
803
      for (i = setValuesSize; i < argsSize; i++) {
10✔
804
        args[i] = whereArgs[i - setValuesSize];
10✔
805
      }
806
    }
807
    if (whereClause != null) {
18✔
808
      update.push(' WHERE ');
10✔
809
      update.push(whereClause);
10✔
810
    }
811
    const sql = update.join('');
18✔
812
    return connection.run(sql, args).changes;
18✔
813
  }
814

815
  /**
816
   * Insert a new row
817
   *
818
   * @param connection connection
819
   * @param table table name
820
   * @param values content values
821
   * @return row id or -1 on an exception
822
   */
823
  public static insert(connection: GeoPackageConnection, table: string, values: ContentValues): number {
1✔
824
    try {
1,050✔
825
      return this.insertOrThrow(connection, table, values);
1,050✔
826
    } catch (e) {
827
      console.error(e);
×
828
      console.warn('Error inserting into table: ' + table + ', Values: ' + values);
×
829
      return -1;
×
830
    }
831
  }
832

833
  /**
834
   * Insert a new row
835
   * @param connection connection
836
   * @param table table name
837
   * @param values content values
838
   * @return row id
839
   */
840
  public static insertOrThrow(connection: GeoPackageConnection, table: string, values: ContentValues): number {
1✔
841
    const insert = [];
1,050✔
842
    insert.push('insert into ');
1,050✔
843
    insert.push(SQLUtils.quoteWrap(table));
1,050✔
844
    insert.push(' (');
1,050✔
845
    const args = [];
1,050✔
846
    const size = values != null && values.size() > 0 ? values.size() : 0;
1,050!
847
    let i = 0;
1,050✔
848
    for (const colName of values.keySet()) {
4,607✔
849
      insert.push(i > 0 ? ',' : '');
4,607✔
850
      insert.push(SQLUtils.quoteWrap(colName));
4,607✔
851
      args.push(values.get(colName));
4,607✔
852
      i++;
4,607✔
853
    }
854
    insert.push(')');
1,050✔
855
    insert.push(' values (');
1,050✔
856
    for (i = 0; i < size; i++) {
1,050✔
857
      insert.push(i > 0 ? ',?' : '?');
4,607✔
858
    }
859
    insert.push(')');
1,050✔
860
    const sql = insert.join('');
1,050✔
861
    return connection.insert(sql, args);
1,050✔
862
  }
863
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc