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

ngageoint / geopackage-js / 3971101424

pending completion
3971101424

push

github

Christopher Caldwell
update linux tile

3572 of 8035 branches covered (44.46%)

Branch coverage included in aggregate %.

15055 of 20471 relevant lines covered (73.54%)

1437.27 hits per line

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

61.83
/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);
33,949✔
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 = '';
309✔
41
    sql = sql
309✔
42
      .concat('CREATE TABLE ')
43
      .concat(StringUtils.quoteWrap(table.getTableName()))
44
      .concat(' (');
45

46
    // Add each column to the sql
47
    const columns = table.getUserColumns().getColumns();
309✔
48
    for (let i = 0; i < columns.length; i++) {
309✔
49
      const column = columns[i];
1,675✔
50
      if (i > 0) {
1,675✔
51
        sql = sql.concat(',');
1,366✔
52
      }
53
      sql = sql.concat('\n  ');
1,675✔
54
      sql = sql.concat(SQLUtils.columnSQL(column));
1,675✔
55
    }
56

57
    // Add unique constraints
58
    table
309✔
59
      .getConstraints()
60
      .all()
61
      .forEach(constraint => {
62
        sql = sql.concat(',\n  ');
39✔
63
        sql = sql.concat(constraint.buildSql());
39✔
64
      });
65

66
    sql = sql.concat('\n);');
309✔
67
    return sql;
309✔
68
  }
69

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

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

89
    sql = sql.concat(column.getType());
1,679✔
90
    if (column.hasMax()) {
1,679✔
91
      sql = sql
28✔
92
        .concat('(')
93
        .concat(column.getMax().toString())
94
        .concat(')');
95
    }
96

97
    column
1,679✔
98
      .getConstraints()
99
      .all()
100
      .forEach(constraint => {
101
        sql = sql.concat(' ');
1,316✔
102
        sql = sql.concat(column.buildConstraintSql(constraint));
1,316✔
103
      });
104

105
    return sql.toString();
1,679✔
106
  }
107

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

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

135
  /**
136
   * Create the foreign keys SQL
137
   * @param on true to turn on, false to turn off
138
   * @return foreign keys SQL
139
   */
140
  static foreignKeysSQL(on: boolean): string {
1✔
141
    return 'PRAGMA foreign_keys = ' + on;
×
142
  }
143

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

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

165
  /**
166
   * Create the foreign key check SQL
167
   * @param tableName table name
168
   * @return foreign key check SQL
169
   */
170
  static foreignKeyCheckSQL(tableName: string): string {
1✔
171
    return (
18✔
172
      'PRAGMA foreign_key_check' +
173
      (tableName !== null && tableName !== undefined ? '(' + StringUtils.quoteWrap(tableName) + ')' : '')
54!
174
    );
175
  }
176

177
  /**
178
   * Create the integrity check SQL
179
   * @return integrity check SQL
180
   */
181
  static integrityCheckSQL(): string {
1✔
182
    return 'PRAGMA integrity_check';
18✔
183
  }
184

185
  /**
186
   * Create the quick check SQL
187
   * @return quick check SQL
188
   */
189
  static quickCheckSQL(): string {
1✔
190
    return 'PRAGMA quick_check';
18✔
191
  }
192

193
  /**
194
   * Drop the table if it exists
195
   * @param db connection
196
   * @param tableName table name
197
   */
198
  static dropTable(db: GeoPackageConnection, tableName: string): void {
1✔
199
    const sql = SQLUtils.dropTableSQL(tableName);
229✔
200
    db.run(sql);
229✔
201
  }
202

203
  /**
204
   * Create the drop table if exists SQL
205
   * @param tableName table name
206
   * @return drop table SQL
207
   */
208
  static dropTableSQL(tableName: string): string {
1✔
209
    return 'DROP TABLE IF EXISTS ' + StringUtils.quoteWrap(tableName);
229✔
210
  }
211

212
  /**
213
   * Drop the view if it exists
214
   * @param db connection
215
   * @param viewName view name
216
   */
217
  static dropView(db: GeoPackageConnection, viewName: string): void {
1✔
218
    const sql = SQLUtils.dropViewSQL(viewName);
×
219
    db.run(sql);
×
220
  }
221

222
  /**
223
   * Create the drop view if exists SQL
224
   * @param viewName view name
225
   * @return drop view SQL
226
   */
227
  static dropViewSQL(viewName: string): string {
1✔
228
    return 'DROP VIEW IF EXISTS ' + StringUtils.quoteWrap(viewName);
×
229
  }
230

231
  /**
232
   * Transfer table content from one table to another
233
   * @param db connection
234
   * @param tableMapping table mapping
235
   */
236
  static transferTableContentForTableMapping(db: GeoPackageConnection, tableMapping: TableMapping): void {
1✔
237
    const sql = SQLUtils.transferTableContentSQL(tableMapping);
39✔
238
    db.run(sql);
39✔
239
  }
240

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

265
      if (column.hasConstantValue()) {
203✔
266
        selectColumns = selectColumns.concat(column.getConstantValueAsString());
28✔
267
      } else {
268
        if (column.hasDefaultValue()) {
175✔
269
          selectColumns = selectColumns.concat('ifnull(');
57✔
270
        }
271
        selectColumns = selectColumns.concat(StringUtils.quoteWrap(column.fromColumn));
175✔
272
        if (column.hasDefaultValue()) {
175✔
273
          selectColumns = selectColumns.concat(',');
57✔
274
          selectColumns = selectColumns.concat(column.getDefaultValueAsString());
57✔
275
          selectColumns = selectColumns.concat(')');
57✔
276
        }
277
      }
278

279
      if (column.hasWhereValue()) {
203✔
280
        if (where.length > 0) {
32✔
281
          where = where.concat(' AND ');
11✔
282
        }
283
        where = where.concat(StringUtils.quoteWrap(column.fromColumn));
32✔
284
        where = where.concat(' ');
32✔
285
        where = where.concat(column.whereOperator);
32✔
286
        where = where.concat(' ');
32✔
287
        where = where.concat(column.getWhereValueAsString());
32✔
288
      }
289
    });
290

291
    insert = insert.concat(') SELECT ');
39✔
292
    insert = insert.concat(selectColumns);
39✔
293
    insert = insert.concat(' FROM ');
39✔
294
    insert = insert.concat(StringUtils.quoteWrap(tableMapping.fromTable));
39✔
295

296
    if (where.length > 0) {
39✔
297
      insert = insert.concat(' WHERE ');
21✔
298
      insert = insert.concat(where);
21✔
299
    }
300

301
    return insert.toString();
39✔
302
  }
303

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

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

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

376
  /**
377
   * Modify the SQL with table mapping modifications
378
   * @param sql SQL statement
379
   * @param tableMapping table mapping
380
   * @return updated SQL, null if SQL contains a deleted column
381
   */
382
  static modifySQLWithTableMapping(sql: string, tableMapping: TableMapping): string {
1✔
383
    let updatedSql = sql;
145✔
384

385
    const droppedColumns = Array.from(tableMapping.droppedColumns);
145✔
386
    for (let i = 0; i < droppedColumns.length; i++) {
145✔
387
      const column = droppedColumns[i];
42✔
388
      const updated = SQLUtils.replaceName(updatedSql, column, ' ');
42✔
389
      if (updated !== null && updated !== undefined) {
42!
390
        updatedSql = null;
×
391
        break;
×
392
      }
393
    }
394

395
    if (updatedSql !== null && updatedSql !== undefined) {
145!
396
      tableMapping.getMappedColumns().forEach(column => {
145✔
397
        if (column.hasNewName()) {
805!
398
          const updated = SQLUtils.replaceName(updatedSql, column.fromColumn, column.toColumn);
×
399
          if (updated !== null && updated !== undefined) {
×
400
            updatedSql = updated;
×
401
          }
402
        }
403
      });
404
    }
405
    return updatedSql;
145✔
406
  }
407

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

420
    // Quick check if contained in the SQL
421
    if (sql.indexOf(name) >= 0) {
42!
422
      let updated = false;
×
423
      let updatedSqlBuilder = '';
×
424

425
      // Split the SQL apart by the name
426
      const parts = sql.split(name);
×
427

428
      for (let i = 0; i <= parts.length; i++) {
×
429
        if (i > 0) {
×
430
          // Find the character before the name
431
          let before = '_';
×
432
          const beforePart = parts[i - 1];
×
433
          if (beforePart.length === 0) {
×
434
            if (i == 1) {
×
435
              // SQL starts with the name, allow
436
              before = ' ';
×
437
            }
438
          } else {
439
            before = beforePart.substring(beforePart.length - 1);
×
440
          }
441

442
          // Find the character after the name
443
          let after = '_';
×
444
          if (i < parts.length) {
×
445
            const afterPart = parts[i];
×
446
            if (afterPart.length !== 0) {
×
447
              after = afterPart.substring(0, 1);
×
448
            }
449
          } else if (sql.endsWith(name)) {
×
450
            // SQL ends with the name, allow
451
            after = ' ';
×
452
          } else {
453
            break;
×
454
          }
455

456
          // Check the before and after characters for non word
457
          // characters
458
          if (before.match('\\W').length > 0 && after.match('\\W').length > 0) {
×
459
            // Replace the name
460
            updatedSqlBuilder = updatedSqlBuilder.concat(replacement);
×
461
            updated = true;
×
462
          } else {
463
            // Preserve the name
464
            updatedSqlBuilder = updatedSqlBuilder.concat(name);
×
465
          }
466
        }
467

468
        // Add the part to the SQL
469
        if (i < parts.length) {
×
470
          updatedSqlBuilder = updatedSqlBuilder.concat(parts[i]);
×
471
        }
472
      }
473
      // Set if the SQL was modified
474
      if (updated) {
×
475
        updatedSql = updatedSqlBuilder.toString();
×
476
      }
477
    }
478
    return updatedSql;
42✔
479
  }
480

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

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

513
      if (db !== null && db !== undefined) {
×
514
        // Check for conflicting SQLite Master table names
515
        while (
×
516
          SQLiteMaster.count(db, null, SQLiteMasterQuery.createForColumnValue(SQLiteMasterColumn.NAME, newName)) > 0
517
        ) {
518
          newName = baseName + '_' + ++count;
×
519
        }
520
      }
521
    }
522
    return newName;
7✔
523
  }
524

525
  /**
526
   * Get the column default value as a string
527
   *
528
   * @param defaultValue default value
529
   * @param dataType data type
530
   * @return default value
531
   */
532
  public static columnDefaultValue(defaultValue: any, dataType: GeoPackageDataType): string {
1✔
533
    return GeoPackageDataType.columnDefaultValue(defaultValue, dataType);
×
534
  }
535

536
  /**
537
   * Rebuild the GeoPackage, repacking it into a minimal amount of disk space
538
   * @param db connection
539
   */
540
  static vacuum(db: GeoPackageConnection): void {
1✔
541
    db.run('VACUUM');
×
542
  }
543

544
  /**
545
   * Execute the SQL
546
   *
547
   * @param connection
548
   *            connection
549
   * @param sql
550
   *            sql statement
551
   */
552
  public static execSQL(connection: GeoPackageConnection, sql: string): void {
1✔
553
    connection.connectionSource.run(sql);
×
554
  }
555

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

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

583
  /**
584
   * Count for query
585
   * @param connection connection
586
   * @param tableName table
587
   * @param where where clause
588
   * @param whereArgs where arguments
589
   * @return number the count of rows
590
   */
591
  public static count(connection: GeoPackageConnection, tableName: string, where: string, whereArgs: [] | Record<string, any>): number {
1✔
592
    return connection.connectionSource.count(tableName, where, whereArgs);
1,009✔
593
  }
594

595
  /**
596
   * Attempt to count the results of the query
597
   * @param connection connection
598
   * @param sql SQL statement
599
   * @param selectionArgs selection arguments
600
   * @return count if known, -1 if not able to determine
601
   */
602
  public static countSqlQuery(connection: GeoPackageConnection | DBAdapter, sql: string, selectionArgs: any[]): number {
1✔
603
    let sqlCommands = [];
1,242✔
604

605
    const upperCaseSQL = sql.toUpperCase();
1,242✔
606
    let afterSelectIndex = upperCaseSQL.indexOf("SELECT") + "SELECT".length;
1,242✔
607
    let upperCaseAfterSelect = upperCaseSQL.substring(afterSelectIndex).trim();
1,242✔
608

609
    if (upperCaseAfterSelect.startsWith("COUNT")) {
1,242!
610
      sqlCommands.push(sql);
×
611
    } else {
612

613
      let fromIndex = upperCaseSQL.indexOf("FROM");
1,242✔
614
      if (upperCaseAfterSelect.startsWith("DISTINCT")) {
1,242!
615

616
        let commaIndex = upperCaseSQL.indexOf(",");
×
617
        if (commaIndex < 0 || commaIndex >= fromIndex) {
×
618

619
          sqlCommands.push(SQLUtils.adjustCount("SELECT COUNT("
×
620
            + sql.substring(afterSelectIndex, fromIndex) + ") "
621
            + sql.substring(fromIndex)));
622

623
          const isNull = ["SELECT COUNT(*) > 0 "];
×
624
          const columnIsNull = sql.substring(upperCaseSQL.indexOf("DISTINCT") + "DISTINCT".length, fromIndex) + "IS NULL";
×
625
          const whereIndex = upperCaseSQL.indexOf("WHERE");
×
626
          let endIndex = sql.indexOf(";");
×
627
          if (endIndex < 0) {
×
628
            endIndex = sql.length;
×
629
          }
630
          if (whereIndex >= 0) {
×
631
            isNull.push(sql.substring(fromIndex, whereIndex + "WHERE".length));
×
632
            isNull.push(columnIsNull);
×
633
            isNull.push(" AND ( ");
×
634
            isNull.push(sql.substring(whereIndex + "WHERE".length, endIndex));
×
635
            isNull.push(" )");
×
636
          } else {
637
            isNull.push(sql.substring(fromIndex, endIndex));
×
638
            isNull.push(" WHERE");
×
639
            isNull.push(columnIsNull);
×
640
          }
641
          sqlCommands.push(SQLUtils.adjustCount(isNull.join('')));
×
642
        }
643

644
      } else if (fromIndex != -1) {
1,242!
645
        sqlCommands.push(SQLUtils.adjustCount("SELECT COUNT(*) " + sql.substring(fromIndex)));
1,242✔
646
      }
647
    }
648

649
    let count = -1;
1,242✔
650
    if (sqlCommands.length === 0) {
1,242!
651
      // Unable to count
652
      console.info("Unable to count query without result iteration. SQL: " + sql + ", args: " + selectionArgs);
×
653
    } else {
654
      count = 0;
1,242✔
655
      for (const sqlCommand of sqlCommands) {
1,242✔
656
        try {
1,242✔
657
          const value = SQLUtils.querySingleResultWithColumnIndex(connection, sqlCommand, selectionArgs, 0);
1,242✔
658
          if (value != null) {
1,242!
659
            count += value as number;
1,242✔
660
          }
661
        } catch (e) {
662
          console.warn("Unable to count query without result iteration. SQL: " + sql + ", args: " + selectionArgs);
×
663
          count = -1;
×
664
        }
665
      }
666
    }
667
    return count;
1,242✔
668
  }
669

670
  /**
671
   * Adjust the count statement as needed
672
   * @param sql sql statement
673
   * @return adjusted or original statement
674
   */
675
  private static adjustCount(sql: string): string {
1✔
676
    const upperCase = sql.toUpperCase();
1,242✔
677
    let limitIndex = upperCase.indexOf(" LIMIT ");
1,242✔
678
    if (limitIndex >= 0) {
1,242!
679
      let lastParenthesis = sql.lastIndexOf(')');
×
680
      if (lastParenthesis == -1 || limitIndex > lastParenthesis) {
×
681
        sql = sql.substring(0, limitIndex);
×
682
      }
683
    }
684
    return sql;
1,242✔
685
  }
686

687
  // /**
688
  //  * Create SQL for adding a column
689
  //  * @param db connection
690
  //  * @param tableName table name
691
  //  * @param column user column
692
  //  */
693
  // public static addColumn(db: GeoPackageConnection, tableName: string, column: UserColumn): void {
694
  //   AlterTable.addColumn(db, tableName, column.getName(), SQLUtils.columnDefinition(column));
695
  // }
696

697
  /**
698
   * Perform the query and wrap as a result
699
   * @param connection connection
700
   * @param sql sql statement
701
   * @param selectionArgs selection arguments
702
   * @return result
703
   */
704
  public static wrapQuery(connection: GeoPackageConnection | DBAdapter, sql: string, selectionArgs: [] | Record<string, any>): ResultSetResult {
1✔
705
    return new ResultSetResult(this.query(connection, sql, selectionArgs));
1,272✔
706
  }
707

708
  /**
709
   * Query the SQL for a single result object with the expected data type
710
   * @param connection connection
711
   * @param sql sql statement
712
   * @param args arguments
713
   * @param columnName column name
714
   * @return result, null if no result
715
   */
716
  public static querySingleResult(connection: GeoPackageConnection, sql: string, args: [] | Record<string, any>, columnName: string): any {
1✔
717
    const result = SQLUtils.wrapQuery(connection, sql, args);
×
718
    const value =  ResultUtils.buildSingleResult(result, columnName);
×
719
    result.close();
×
720
    return value;
×
721
  }
722

723
  /**
724
   * Query the SQL for a single result object with the expected data type
725
   * @param connection connection
726
   * @param sql sql statement
727
   * @param args arguments
728
   * @param columnIdx column index
729
   * @return result, null if no result
730
   */
731
  public static querySingleResultWithColumnIndex(connection: GeoPackageConnection | DBAdapter, sql: string, args: [] | Record<string, any>, columnIdx: number = 0): any {
1,272!
732
    const result = SQLUtils.wrapQuery(connection, sql, args);
1,272✔
733
    const value = ResultUtils.buildSingleResultWithColumnIndex(result, columnIdx);
1,272✔
734
    result.close();
1,272✔
735
    return value;
1,272✔
736
  }
737

738
  /**
739
   * Execute a deletion
740
   * @param connection connection
741
   * @param table table name
742
   * @param where where clause
743
   * @param args where arguments
744
   * @return deleted count
745
   */
746
  public static delete(connection: GeoPackageConnection, table: string, where: string, args: []): number {
1✔
747
    return connection.delete(table, where, args);
×
748
  }
749

750
  /**
751
   * Update table rows
752
   * @param connection connection
753
   * @param table table name
754
   * @param values content values
755
   * @param whereClause where clause
756
   * @param whereArgs where arguments
757
   * @return updated count
758
   */
759
  public static update(connection: GeoPackageConnection, table: string, values: ContentValues, whereClause: string, whereArgs: DBValue[]): number {
1✔
760
    const update = [];
18✔
761
    update.push("update ");
18✔
762
    update.push(SQLUtils.quoteWrap(table));
18✔
763
    update.push(" set ");
18✔
764
    const setValuesSize = values.size();
18✔
765
    const argsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
18✔
766
    const args = [];
18✔
767
    let i = 0;
18✔
768
    for (const colName of values.keySet()) {
102✔
769
      update.push((i > 0) ? "," : "");
102✔
770
      update.push(SQLUtils.quoteWrap(colName));
102✔
771
      args.push(values.get(colName));
102✔
772
      update.push(" = ?");
102✔
773
      i++;
102✔
774
    }
775
    if (whereArgs != null) {
18✔
776
      for (i = setValuesSize; i < argsSize; i++) {
10✔
777
        args[i] = whereArgs[i - setValuesSize];
10✔
778
      }
779
    }
780
    if (whereClause != null) {
18✔
781
      update.push(" WHERE ");
10✔
782
      update.push(whereClause);
10✔
783
    }
784
    const sql = update.join('');
18✔
785
    return connection.run(sql, args).changes;
18✔
786
  }
787

788
  /**
789
   * Insert a new row
790
   *
791
   * @param connection connection
792
   * @param table table name
793
   * @param values content values
794
   * @return row id or -1 on an exception
795
   */
796
  public static insert(connection: GeoPackageConnection, table: string, values: ContentValues): number {
1✔
797
    try {
745✔
798
      return this.insertOrThrow(connection, table, values);
745✔
799
    } catch (e) {
800
      console.error(e);
×
801
      console.warn("Error inserting into table: " + table + ", Values: " + values);
×
802
      return -1;
×
803
    }
804
  }
805

806
  /**
807
   * Insert a new row
808
   * @param connection connection
809
   * @param table table name
810
   * @param values content values
811
   * @return row id
812
   */
813
  public static insertOrThrow(connection: GeoPackageConnection, table: string, values: ContentValues): number {
1✔
814
    const insert = [];
745✔
815
    insert.push('insert into ');
745✔
816
    insert.push(SQLUtils.quoteWrap(table));
745✔
817
    insert.push(' (');
745✔
818
    let args = [];
745✔
819
    let size = (values != null && values.size() > 0) ? values.size() : 0;
745!
820
    let i = 0;
745✔
821
    for (const colName of values.keySet()) {
3,578✔
822
      insert.push((i > 0) ? "," : "");
3,578✔
823
      insert.push(SQLUtils.quoteWrap(colName));
3,578✔
824
      args.push(values.get(colName));
3,578✔
825
      i++;
3,578✔
826
    }
827
    insert.push(')');
745✔
828
    insert.push(" values (");
745✔
829
    for (i = 0; i < size; i++) {
745✔
830
      insert.push((i > 0) ? ",?" : "?");
3,578✔
831
    }
832
    insert.push(')');
745✔
833
    const sql = insert.join('');
745✔
834
    return connection.insert(sql, args);
745✔
835
  }
836
}
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