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

ngageoint / geopackage-js / 4078143969

pending completion
4078143969

push

github

Christopher Caldwell
bump version

3593 of 8015 branches covered (44.83%)

Branch coverage included in aggregate %.

15102 of 20471 relevant lines covered (73.77%)

1564.55 hits per line

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

84.69
/lib/user/userColumns.ts
1
/**
2
 * Abstract collection of columns from a user table, representing a full set of
3
 * table columns or a subset from a query
4
 * @param <TColumn> column type
5
 */
6
import { UserColumn } from './userColumn';
7
import { GeoPackageDataType } from '../db/geoPackageDataType';
1✔
8
import { GeoPackageException } from '../geoPackageException';
1✔
9

10
export abstract class UserColumns<TColumn extends UserColumn> {
1✔
11
  /**
12
   * Table name, null when a pre-ordered subset of columns for a query
13
   */
14
  private tableName: string;
15

16
  /**
17
   * Array of column names
18
   */
19
  private columnNames: string[];
20

21
  /**
22
   * List of columns
23
   */
24
  private readonly columns: TColumn[];
25

26
  /**
27
   * Custom column specification flag (subset of table columns or different
28
   * ordering)
29
   */
30
  private custom: boolean;
31

32
  /**
33
   * Mapping between (lower cased) column names and their index
34
   */
35
  private readonly nameToIndex: Map<string, number>;
36

37
  /**
38
   * Primary key column index
39
   */
40
  private pkIndex: number;
41

42
  /**
43
   * Indicates if the primary key is modifiable
44
   */
45
  private pkModifiable = false;
9,280✔
46

47
  /**
48
   * Indicates if values are validated against column types
49
   */
50
  private valueValidation = true;
9,280✔
51

52
  /**
53
   * Constructor
54
   * @param tableName table name
55
   * @param columns columns
56
   * @param custom custom column specification
57
   */
58
  protected constructor(tableName: string, columns: TColumn[], custom: boolean);
59

60
  /**
61
   * Copy Constructor
62
   *
63
   * @param userColumns
64
   *            user columns
65
   */
66
  protected constructor(userColumns: UserColumns<TColumn>);
67

68
  /**
69
   * Constructor
70
   * @param args
71
   * @protected
72
   */
73
  protected constructor(...args) {
46,400✔
74
    if (args.length === 1) {
9,280!
75
      const userColumns = args[0];
×
76
      this.tableName = userColumns.tableName;
×
77
      this.columnNames = userColumns.columnNames.slice();
×
78
      this.columns = [];
×
79
      for (const column of userColumns.columns) {
×
80
        const copiedColumn = column.copy() as TColumn;
×
81
        this.columns.push(copiedColumn);
×
82
      }
83
      this.nameToIndex = new Map<string, number>(userColumns.nameToIndex);
×
84
      this.pkIndex = userColumns.pkIndex;
×
85
      this.pkModifiable = userColumns.pkModifiable;
×
86
      this.valueValidation = userColumns.valueValidation;
×
87
    } else if (args.length === 3) {
9,280!
88
      this.tableName = args[0];
9,280✔
89
      this.columns = args[1];
9,280✔
90
      this.custom = args[2];
9,280✔
91
      this.nameToIndex = new Map<string, number>();
9,280✔
92
    }
93
  }
94

95
  /**
96
   * Copy the user columns
97
   *
98
   * @return copied user columns
99
   */
100
  public abstract copy(): UserColumns<TColumn>;
101

102
  /**
103
   * Update the table columns
104
   */
105
  updateColumns(): void {
9,310✔
106
    this.nameToIndex.clear();
9,310✔
107
    if (!this.custom) {
9,310✔
108
      const indices = new Set<number>();
6,799✔
109

110
      // Check for missing indices and duplicates
111
      const needsIndex = [];
6,799✔
112
      this.columns.forEach((column) => {
6,799✔
113
        if (column.hasIndex()) {
29,827✔
114
          const index = column.getIndex();
25,953✔
115
          if (indices.has(index)) {
25,953✔
116
            throw new GeoPackageException('Duplicate index: ' + index + ', Table Name: ' + this.tableName);
1✔
117
          } else {
118
            indices.add(index);
25,952✔
119
          }
120
        } else {
121
          needsIndex.push(column);
3,874✔
122
        }
123
      });
124

125
      // Update columns that need an index
126
      let currentIndex = -1;
6,798✔
127
      needsIndex.forEach((column) => {
6,798✔
128
        while (indices.has(++currentIndex)) {}
3,874✔
129
        column.setIndex(currentIndex);
3,874✔
130
      });
131

132
      // Sort the columns by index
133
      this.columns.sort((a, b) => {
6,798✔
134
        return a.getIndex() - b.getIndex();
23,301✔
135
      });
136
    }
137

138
    this.pkIndex = -1;
9,309✔
139
    this.columnNames = [];
9,309✔
140

141
    for (let index = 0; index < this.columns.length; index++) {
9,309✔
142
      const column = this.columns[index];
42,847✔
143
      const columnName = column.getName();
42,847✔
144
      const lowerCaseColumnName = columnName.toLowerCase();
42,847✔
145

146
      if (!this.custom) {
42,847✔
147
        if (column.getIndex() != index) {
29,823✔
148
          throw new GeoPackageException('No column found at index: ' + index + ', Table Name: ' + this.tableName);
1✔
149
        }
150

151
        if (this.nameToIndex.has(lowerCaseColumnName)) {
29,822✔
152
          throw new GeoPackageException(
1✔
153
            'Duplicate column found at index: ' + index + ', Table Name: ' + this.tableName + ', Name: ' + columnName,
154
          );
155
        }
156
      }
157

158
      if (column.isPrimaryKey()) {
42,845✔
159
        if (this.pkIndex != -1) {
7,378✔
160
          let error = 'More than one primary key column was found for ';
3✔
161
          if (this.custom) {
3✔
162
            error = error.concat('custom specified table columns');
1✔
163
          } else {
164
            error = error.concat('table');
2✔
165
          }
166
          error = error.concat('. table: ' + this.tableName + ', index1: ' + this.pkIndex + ', index2: ' + index);
3✔
167
          if (this.custom) {
3✔
168
            error = error.concat(', columns: ' + this.columnNames);
1✔
169
          }
170
          throw new GeoPackageException(error);
3✔
171
        }
172
        this.pkIndex = index;
7,375✔
173
      }
174

175
      this.columnNames[index] = columnName;
42,842✔
176
      this.nameToIndex.set(lowerCaseColumnName, index);
42,842✔
177
    }
178
  }
179

180
  /**
181
   * Is the primary key modifiable
182
   *
183
   * @return true if the primary key is modifiable
184
   */
185
  public isPkModifiable(): boolean {
1✔
186
    return this.pkModifiable;
12✔
187
  }
188

189
  /**
190
   * Set if the primary key can be modified
191
   *
192
   * @param pkModifiable
193
   *            primary key modifiable flag
194
   */
195
  public setPkModifiable(pkModifiable: boolean): void {
1✔
196
    this.pkModifiable = pkModifiable;
×
197
  }
198

199
  /**
200
   * Is value validation against column types enabled
201
   *
202
   * @return true if values are validated against column types
203
   */
204
  public isValueValidation(): boolean {
1✔
205
    return this.valueValidation;
4,561✔
206
  }
207

208
  /**
209
   * Set if values should validated against column types
210
   *
211
   * @param valueValidation
212
   *            value validation flag
213
   */
214
  public setValueValidation(valueValidation: boolean): void {
1✔
215
    this.valueValidation = valueValidation;
×
216
  }
217

218
  /**
219
   * Check for duplicate column names
220
   *
221
   * @param index index
222
   * @param previousIndex previous index
223
   * @param column column
224
   */
225
  duplicateCheck(index: number, previousIndex: number, column: string): void {
1✔
226
    if (previousIndex !== null && previousIndex !== undefined) {
335!
227
      throw new GeoPackageException(
×
228
        'More than one ' +
229
          column +
230
          " column was found for table '" +
231
          this.tableName +
232
          "'. Index " +
233
          previousIndex +
234
          ' and ' +
235
          index,
236
      );
237
    }
238
  }
239

240
  /**
241
   * Check for the expected data type
242
   * @param expected expected data type
243
   * @param column user column
244
   */
245
  typeCheck(expected: GeoPackageDataType, column: TColumn): void {
1✔
246
    const actual = column.getDataType();
3,072✔
247
    if (actual === null || actual === undefined || actual !== expected) {
3,072!
248
      throw new GeoPackageException(
×
249
        'Unexpected ' +
250
          column.getName() +
251
          " column data type was found for table '" +
252
          this.tableName +
253
          "', expected: " +
254
          GeoPackageDataType.nameFromType(expected) +
255
          ', actual: ' +
256
          (actual !== null && actual !== undefined ? GeoPackageDataType.nameFromType(actual) : 'null'),
×
257
      );
258
    }
259
  }
260

261
  /**
262
   * Check for missing columns
263
   * @param index column index
264
   * @param column user column
265
   */
266
  missingCheck(index: number, column: string): void {
1✔
267
    if (index === null || index === undefined) {
2,934!
268
      throw new GeoPackageException('No ' + column + " column was found for table '" + this.tableName + "'");
×
269
    }
270
  }
271

272
  /**
273
   * Get the column index of the column name
274
   * @param columnName column name
275
   * @return column index
276
   */
277
  getColumnIndexForColumnName(columnName: string): number {
1✔
278
    return this.getColumnIndex(columnName, true);
31,211✔
279
  }
280

281
  /**
282
   * Get the column index of the column name
283
   * @param columnName column name
284
   * @param required column existence is required
285
   * @return column index
286
   */
287
  getColumnIndex(columnName: string, required: boolean): number {
1✔
288
    const index = this.nameToIndex.get(columnName.toLowerCase());
35,046✔
289
    if (required && (index === null || index === undefined)) {
35,046✔
290
      let error = 'Column does not exist in ';
6✔
291
      if (this.custom) {
6!
292
        error = error.concat('custom specified table columns');
×
293
      } else {
294
        error = error.concat('table');
6✔
295
      }
296
      error = error.concat('. table: ' + this.tableName + ', column: ' + columnName);
6✔
297
      if (this.custom) {
6!
298
        error = error.concat(', columns: ' + this.columnNames);
×
299
      }
300
      throw new GeoPackageException(error);
6✔
301
    }
302
    return index;
35,040✔
303
  }
304

305
  /**
306
   * Get the array of column names
307
   * @return column names
308
   */
309
  getColumnNames(): string[] {
1✔
310
    return this.columnNames;
4,034✔
311
  }
312

313
  /**
314
   * Get the column name at the index
315
   * @param index column index
316
   * @return column name
317
   */
318
  getColumnName(index: number): string {
1✔
319
    return this.columnNames[index];
2,232✔
320
  }
321

322
  /**
323
   * Get the list of columns
324
   * @return columns
325
   */
326
  getColumns(): TColumn[] {
1✔
327
    return this.columns;
12,995✔
328
  }
329

330
  /**
331
   * Get the column at the index
332
   * @param index column index
333
   * @return column
334
   */
335
  getColumnForIndex(index: number): TColumn {
1✔
336
    return this.columns[index];
75,478✔
337
  }
338

339
  /**
340
   * Get the column of the column name
341
   * @param columnName column name
342
   * @return column
343
   */
344
  getColumn(columnName: string): TColumn {
1✔
345
    return this.getColumnForIndex(this.getColumnIndexForColumnName(columnName));
16,974✔
346
  }
347

348
  /**
349
   * Check if the table has the column
350
   * @param columnName column name
351
   * @return true if has the column
352
   */
353
  hasColumn(columnName: string): boolean {
1✔
354
    return this.nameToIndex.has(columnName.toLowerCase());
3,693✔
355
  }
356

357
  /**
358
   * Get the column count
359
   * @return column count
360
   */
361
  columnCount(): number {
1✔
362
    return this.columns.length;
42,730✔
363
  }
364

365
  /**
366
   * Get the table name
367
   * @return table name
368
   */
369
  getTableName(): string {
1✔
370
    return this.tableName;
11,113✔
371
  }
372

373
  /**
374
   * Set the table name
375
   * @param tableName table name
376
   */
377
  setTableName(tableName: string): void {
1✔
378
    this.tableName = tableName;
×
379
  }
380

381
  /**
382
   * Is custom column specification (partial and/or ordering)
383
   * @return custom flag
384
   */
385
  isCustom(): boolean {
1✔
386
    return this.custom;
7,612✔
387
  }
388

389
  /**
390
   * Set the custom column specification flag
391
   * @param custom custom flag
392
   */
393
  setCustom(custom: boolean): void {
1✔
394
    this.custom = custom;
×
395
  }
396

397
  /**
398
   * Check if the table has a primary key column
399
   * @return true if has a primary key
400
   */
401
  hasPkColumn(): boolean {
1✔
402
    return this.pkIndex >= 0;
1,704✔
403
  }
404

405
  /**
406
   * Get the primary key column index
407
   * @return primary key column index
408
   */
409
  getPkColumnIndex(): number {
1✔
410
    return this.pkIndex;
12,617✔
411
  }
412

413
  /**
414
   * Get the primary key column
415
   * @return primary key column
416
   */
417
  getPkColumn(): TColumn {
1✔
418
    let column = null;
1,704✔
419
    if (this.hasPkColumn()) {
1,704!
420
      column = this.columns[this.pkIndex];
1,704✔
421
    }
422
    return column;
1,704✔
423
  }
424

425
  /**
426
   * Get the primary key column name
427
   * @return primary key column name
428
   */
429
  getPkColumnName(): string {
1✔
430
    return this.getPkColumn().getName();
1,009✔
431
  }
432

433
  /**
434
   * Get the columns with the provided data type
435
   * @param type data type
436
   * @return columns
437
   */
438
  columnsOfType(type: GeoPackageDataType): TColumn[] {
1✔
439
    return this.columns.filter((column) => column.getDataType() === type);
6✔
440
  }
441

442
  /**
443
   * Add a new column
444
   * @param column new column
445
   */
446
  addColumn(column: TColumn): void {
1✔
447
    this.columns.push(column);
12✔
448
    this.updateColumns();
12✔
449
  }
450

451
  /**
452
   * Rename a column
453
   * @param column column
454
   * @param newColumnName new column name
455
   */
456
  renameColumn(column: TColumn, newColumnName: string): void {
1✔
457
    this.renameColumnWithName(column.getName(), newColumnName);
1✔
458
    column.setName(newColumnName);
1✔
459
  }
460

461
  /**
462
   * Rename a column
463
   * @param columnName column name
464
   * @param newColumnName new column name
465
   */
466
  renameColumnWithName(columnName: string, newColumnName: string): void {
1✔
467
    this.renameColumnWithIndex(this.getColumnIndexForColumnName(columnName), newColumnName);
2✔
468
  }
469

470
  /**
471
   * Rename a column
472
   * @param index column index
473
   * @param newColumnName new column name
474
   */
475
  renameColumnWithIndex(index: number, newColumnName: string): void {
1✔
476
    this.columns[index].setName(newColumnName);
3✔
477
    this.updateColumns();
3✔
478
  }
479

480
  /**
481
   * Drop a column
482
   * @param column column to drop
483
   */
484
  dropColumn(column: TColumn): void {
1✔
485
    this.dropColumnWithIndex(column.getIndex());
1✔
486
  }
487

488
  /**
489
   * Drop a column
490
   * @param columnName column name
491
   */
492
  dropColumnWithName(columnName: string): void {
1✔
493
    this.dropColumnWithIndex(this.getColumnIndexForColumnName(columnName));
7✔
494
  }
495

496
  /**
497
   * Drop a column
498
   * @param index column index
499
   */
500
  dropColumnWithIndex(index: number): void {
1✔
501
    this.columns.splice(index, 1);
9✔
502
    this.columns.forEach((column) => column.resetIndex());
42✔
503
    this.updateColumns();
9✔
504
  }
505

506
  /**
507
   * Alter a column
508
   * @param column altered column
509
   */
510
  alterColumn(column: TColumn): void {
1✔
511
    const existingColumn = this.getColumn(column.getName());
3✔
512
    const index = existingColumn.getIndex();
3✔
513
    column.setIndex(index);
3✔
514
    this.columns[index] = column;
3✔
515
  }
516
}
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