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

visgl / loaders.gl / 25798238260

13 May 2026 12:10PM UTC coverage: 60.607% (+0.3%) from 60.27%
25798238260

push

github

web-flow
feat(json) GeoJSON -> geoarrow, schema, logging  (#3399)

13466 of 24516 branches covered (54.93%)

Branch coverage included in aggregate %.

448 of 541 new or added lines in 12 files covered. (82.81%)

1264 existing lines in 117 files now uncovered.

27516 of 43103 relevant lines covered (63.84%)

15056.99 hits per line

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

84.71
/modules/sql/src/sql-utils.ts
1
// loaders.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import * as arrow from 'apache-arrow';
6
import type {ArrowTable, DataType, Field, ObjectRowTable, Schema} from '@loaders.gl/schema';
7
import {makeTableFromData, getDataTypeFromArray} from '@loaders.gl/schema-utils';
8

9
import type {SQLColumnInfo} from './sql-types';
10

11
/** Returns true when the current runtime is Node.js. */
12
export function isNodeRuntime(): boolean {
13
  return typeof process !== 'undefined' && Boolean(process.versions?.node);
10✔
14
}
15

16
/** Escapes a SQL string literal using single-quote doubling. */
17
export function escapeSqlString(value: string): string {
18
  return value.replace(/'/g, "''");
4✔
19
}
20

21
/** Wraps an identifier in double quotes, escaping embedded quotes. */
22
export function quoteSqlIdentifier(identifier: string): string {
23
  return `"${identifier.replace(/"/g, '""')}"`;
8✔
24
}
25

26
/** Converts object rows into a loaders.gl Arrow table. */
27
export function convertRowsToArrowTable(rows: Record<string, unknown>[]): ArrowTable {
28
  const table: ObjectRowTable =
29
    rows.length > 0
9✔
30
      ? makeTableFromData(rows)
31
      : {shape: 'object-row-table', schema: {fields: [], metadata: {}}, data: rows};
32
  const schema = deducePrimitiveAwareSchema(table);
9✔
33

34
  if (table.data.length === 0 || schema.fields.length === 0) {
9✔
35
    return {
2✔
36
      shape: 'arrow-table',
37
      schema,
38
      data: new arrow.Table(new arrow.Schema([]))
39
    };
40
  }
41

42
  return {
7✔
43
    shape: 'arrow-table',
44
    schema,
45
    data: arrow.tableFromJSON(table.data)
46
  };
47
}
48

49
/** Converts normalized SQL columns into a loaders.gl schema. */
50
export function convertSQLColumnsToSchema(columns: SQLColumnInfo[]): Schema {
51
  return {
3✔
52
    metadata: {},
53
    fields: columns
54
      .sort((left, right) => (left.ordinalPosition || 0) - (right.ordinalPosition || 0))
5!
55
      .map(column => ({
7✔
56
        name: column.columnName,
57
        type: mapSQLTypeToDataType(column.sqlType),
58
        nullable: column.nullable,
59
        metadata: {
60
          sqlType: column.sqlType
61
        }
62
      }))
63
  };
64
}
65

66
function mapSQLTypeToDataType(sqlType: string): DataType {
67
  const normalizedType = sqlType.toUpperCase();
7✔
68
  if (
7✔
69
    normalizedType.includes('BIGINT') ||
17✔
70
    normalizedType === 'HUGEINT' ||
71
    normalizedType === 'LONG'
72
  ) {
73
    return 'int64';
2✔
74
  }
75
  if (
5✔
76
    normalizedType.includes('INT') ||
17✔
77
    normalizedType === 'INTEGER' ||
78
    normalizedType === 'SMALLINT' ||
79
    normalizedType === 'TINYINT'
80
  ) {
UNCOV
81
    return 'int32';
1✔
82
  }
83
  if (
4!
84
    normalizedType.includes('DOUBLE') ||
12✔
85
    normalizedType.includes('FLOAT8') ||
86
    normalizedType.includes('REAL')
87
  ) {
88
    return 'float64';
×
89
  }
90
  if (
4!
91
    normalizedType.includes('FLOAT') ||
12✔
92
    normalizedType.includes('DECIMAL') ||
93
    normalizedType.includes('NUMERIC')
94
  ) {
95
    return 'float64';
×
96
  }
97
  if (normalizedType.includes('BOOL')) {
4!
98
    return 'bool';
×
99
  }
100
  if (normalizedType.includes('DATE')) {
4!
101
    return 'date-day';
×
102
  }
103
  if (normalizedType.includes('TIME') || normalizedType.includes('TIMESTAMP')) {
4✔
104
    return 'timestamp-millisecond';
2✔
105
  }
106
  if (normalizedType.includes('BLOB') || normalizedType.includes('BINARY')) {
2!
107
    return 'binary';
2✔
108
  }
109
  return 'utf8';
×
110
}
111

112
function deducePrimitiveAwareSchema(table: ObjectRowTable): Schema {
113
  return {
9✔
114
    ...table.schema!,
115
    fields: table.schema!.fields.map((field): Field => {
116
      if (field.type !== 'null') {
9✔
117
        return field;
5✔
118
      }
119

120
      const columnValues = table.data.map(row => row[field.name]);
8✔
121
      const inferredType = getDataTypeFromArray(columnValues);
4✔
122
      return {...field, type: inferredType.type, nullable: inferredType.nullable};
4✔
123
    })
124
  };
125
}
126

127
/** Returns a fully qualified SQL object name. */
128
export function getQualifiedTableName(options: {
129
  catalogName?: string;
130
  schemaName?: string;
131
  tableName: string;
132
}): string {
133
  const identifiers = [options.catalogName, options.schemaName, options.tableName].filter(
2✔
134
    (value): value is string => Boolean(value)
6✔
135
  );
136
  return identifiers.map(identifier => quoteSqlIdentifier(identifier)).join('.');
6✔
137
}
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