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

MrSwitch / dare / #76

11 Jun 2026 10:40PM UTC coverage: 70.912%. First build
#76

Pull #457

MrSwitch
feat(sqlite): init support
Pull Request #457: feat(sqlite): init support

373 of 531 branches covered (70.24%)

Branch coverage included in aggregate %.

207 of 211 new or added lines in 3 files covered. (98.1%)

3664 of 5162 relevant lines covered (70.98%)

6.44 hits per line

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

98.1
/src/sqlite.js
1
import SQL, {join} from 'sql-template-tag';
5✔
2
import Dare from './index.js';
5✔
3

5✔
4
/**
5✔
5
 * SQLiteDare
5✔
6
 * Extends Dare with SQLite-specific overrides
5✔
7
 *
5✔
8
 * @param {object} options - Initial options defining the instance
5✔
9
 * @returns {import('./index.js').default} instance of SQLiteDare
5✔
10
 */
5✔
11
function SQLiteDare(options = {}) {
9✔
12
        const instance = new Dare({
9✔
13
                ...options,
9✔
14
                engine: options.engine || 'sqlite:3',
9✔
15
        });
9✔
16
        Object.setPrototypeOf(instance, SQLiteDare.prototype);
9✔
17
        return instance;
9✔
18
}
9✔
19

5✔
20
// Inherit from Dare
5✔
21
SQLiteDare.prototype = Object.create(Dare.prototype);
5✔
22
SQLiteDare.prototype.constructor = SQLiteDare;
5✔
23

5✔
24
/**
5✔
25
 * Default engine for SQLite
5✔
26
 * @type {string}
5✔
27
 */
5✔
28
SQLiteDare.prototype.engine = 'sqlite:3';
5✔
29

5✔
30
/**
5✔
31
 * SQLite uses `id` as the rowid (alias for rowid)
5✔
32
 * @type {string}
5✔
33
 */
5✔
34
SQLiteDare.prototype.rowid = 'id';
5✔
35

5✔
36
/**
5✔
37
 * SQLite uses LIKE (case-insensitive for ASCII by default)
5✔
38
 * @type {string}
5✔
39
 */
5✔
40
SQLiteDare.prototype.sql_keyword_like = 'LIKE';
5✔
41

5✔
42
/**
5✔
43
 * SQLite JSON EXTRACT prefix
5✔
44
 * @type {string}
5✔
45
 */
5✔
46
SQLiteDare.prototype.sql_json_extract_prefix = '$';
5✔
47

5✔
48
/**
5✔
49
 * SQLite JSON EXTRACT operator
5✔
50
 * @type {string}
5✔
51
 */
5✔
52
SQLiteDare.prototype.sql_json_extract_operator = '->';
5✔
53

5✔
54
/**
5✔
55
 * Sql_json_array - SQLite JSON_ARRAY
5✔
56
 * @type {Dare['sql_json_array']}
5✔
57
 */
5✔
58
SQLiteDare.prototype.sql_json_array = function sql_json_array(expressions) {
5✔
59
        return `JSON_ARRAY(${expressions.join(',')})`;
2✔
60
};
5✔
61

5✔
62
/**
5✔
63
 * SQL Array Agg - SQLite uses JSON_GROUP_ARRAY
5✔
64
 * @type {Dare['sql_json_arrayagg']}
5✔
65
 */
5✔
66
SQLiteDare.prototype.sql_json_arrayagg = function sql_json_arrayagg({
5✔
67
        sql_alias,
1✔
68
        expression,
1✔
69
}) {
1✔
70
        const condition = `CASE WHEN (${sql_alias}.${this.rowid} IS NOT NULL) THEN (${expression}) ELSE NULL END`;
1✔
71
        return `JSON_GROUP_ARRAY(${condition})`;
1✔
72
};
5✔
73

5✔
74
/**
5✔
75
 * SQLite does not support CTE LIMIT filtering
5✔
76
 * @type {Dare['applyCTELimitFiltering']}
5✔
77
 */
5✔
78
SQLiteDare.prototype.applyCTELimitFiltering = function () {
5✔
NEW
79
        return false;
×
80
};
5✔
81

5✔
82
/**
5✔
83
 * Apply limit on DML - SQLite supports LIMIT on DELETE but not in all contexts
5✔
84
 * @type {boolean}
5✔
85
 */
5✔
86
SQLiteDare.prototype.applyLimitOnDML = false;
5✔
87

5✔
88
/**
5✔
89
 * SQLite does not allow joining onto the table being modified in patch / delete requests
5✔
90
 * To work around this, we need to use subquery joins
5✔
91
 * @type {boolean}
5✔
92
 */
5✔
93
SQLiteDare.prototype.applySubqueryOnDML = true;
5✔
94

5✔
95
/**
5✔
96
 * Apply aliases to UPDATE statements - SQLite doesn't support this
5✔
97
 * @type {boolean}
5✔
98
 */
5✔
99
SQLiteDare.prototype.applyAliasesOnUpdate = false;
5✔
100

5✔
101
/**
5✔
102
 * SQLite does not support UPDATE tbl alias SET ...
5✔
103
 * @type {boolean}
5✔
104
 */
5✔
105
SQLiteDare.prototype.applyTableAliasOnUpdate = false;
5✔
106

5✔
107
/**
5✔
108
 * SQL insert suffix - SQLite uses RETURNING clause
5✔
109
 * @type {string}
5✔
110
 */
5✔
111
SQLiteDare.prototype.sql_insert_suffix = ` RETURNING id`;
5✔
112

5✔
113
/**
5✔
114
 * IdentifierWrapper - SQLite uses double quotes for identifiers
5✔
115
 * @type {Dare['identifierWrapper']}
5✔
116
 */
5✔
117
SQLiteDare.prototype.identifierWrapper = function identifierWrapper(field) {
5✔
118
        return ['"', field, '"'].join('');
21✔
119
};
5✔
120

5✔
121
/**
5✔
122
 * SQLite does not support DEFAULT keyword in VALUES, use NULL instead
5✔
123
 */
5✔
124
SQLiteDare.prototype.sql_default_value = null;
5✔
125

5✔
126
/**
5✔
127
 * On Duplicate Keys Update - SQLite uses ON CONFLICT with DO UPDATE/DO NOTHING
5✔
128
 * @type {Dare['onDuplicateKeysUpdate']}
5✔
129
 */
5✔
130
SQLiteDare.prototype.onDuplicateKeysUpdate = function onDuplicateKeysUpdate({
5✔
131
        keys = [],
3✔
132
        existing = [],
3✔
133
        duplicate_keys,
3✔
134
}) {
3✔
135
        if (!keys.length) {
3✔
136
                return `ON CONFLICT DO NOTHING`;
1✔
137
        }
1✔
138

2✔
139
        let conflictKeys;
2✔
140

2✔
141
        if (Array.isArray(duplicate_keys) && duplicate_keys.length) {
3✔
142
                conflictKeys = duplicate_keys;
1✔
143
        } else {
1✔
144
                conflictKeys = existing.filter(item => !keys.includes(item));
1✔
145

1✔
146
                if (!conflictKeys.length) {
1✔
147
                        conflictKeys.push(this.rowid);
1✔
148
                }
1✔
149
        }
1✔
150

2✔
151
        return `
2✔
152
                        ON CONFLICT (${conflictKeys.map(key => this.identifierWrapper(key)).join(',')})
2✔
153
                                DO UPDATE
2✔
154
                                        SET ${keys.map(name => `${this.identifierWrapper(name)}=EXCLUDED.${this.identifierWrapper(name)}`).join(',')}
2✔
155
                `;
2✔
156
};
5✔
157

5✔
158
/**
5✔
159
 * FulltextSearch - SQLite implementation using FTS5 (if available) or LIKE fallback
5✔
160
 * SQLite FTS5 requires a virtual table, so we fall back to LIKE-based search
5✔
161
 * @type {Dare['fulltextSearch']}
5✔
162
 */
5✔
163
SQLiteDare.prototype.fulltextSearch = function fulltextSearch(
5✔
164
        sql_field_array,
1✔
165
        value,
1✔
166
        NOT
1✔
167
) {
1✔
168
        // Use LIKE-based fallback for fulltext search in SQLite
1✔
169
        const terms = value.trim().split(/\s+/).filter(Boolean);
1✔
170
        const conditions = terms.map(term => {
1✔
171
                const likeConditions = sql_field_array.map(
1✔
172
                        field => SQL`${field} LIKE ${`%${term}%`}`
1✔
173
                );
1✔
174
                return SQL`(${join(likeConditions, ' OR ')})`;
1✔
175
        });
1✔
176
        return SQL`${NOT}(${join(conditions, ' AND ')})`;
1✔
177
};
5✔
178

5✔
179
/**
5✔
180
 * Pass through value verbatim for JSON formatting
5✔
181
 * @type {Dare['jsonFormatValue']}
5✔
182
 */
5✔
183
SQLiteDare.prototype.jsonFormatValue = function jsonFormatValue(value) {
5✔
184
        if (Array.isArray(value)) {
1!
NEW
185
                return value.map(item => this.jsonFormatValue(item));
×
NEW
186
        }
×
187
        return value;
1✔
188
};
5✔
189

5✔
190
export default SQLiteDare;
5✔
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