squire_sqlite3_src/
lib.rs

1//! # squire-sqlite3-src
2//!
3//! This crate bundles the [SQLite][] source code for [Squire][]. When Squire’s
4//! `bundled` feature flag is enabled, SQLite is built from the `sqlite3.c` file
5//! included in this crate, and linked into the [`squire-sqlite3-sys`][sys]
6//! crate.
7//!
8//! Users of Squire don’t need to be aware of or interact with this crate. Cargo
9//! will automatically download and build it when you enable Squire’s `bundled`
10//! feature.
11//!
12//! [SQLite]: https://sqlite.org/
13//! [Squire]: https://github.com/silverlyra/squire
14//! [sys]: https://lib.rs/squire-sqlite3-sys
15
16use std::{collections::HashMap, env, iter, ops::Deref, path::PathBuf};
17
18use strum::{EnumDiscriminants, IntoDiscriminant};
19
20/// Build the bundled SQLite sources, using the given [`Config`].
21pub fn build(location: Location, config: impl AsRef<Config>) -> Build {
22    let config = config.as_ref();
23
24    let mut compiler = cc::Build::new();
25    compiler.file(location.input());
26
27    config.apply(&mut compiler);
28    compiler.warnings(false);
29
30    compiler.out_dir(&location.dest);
31    compiler.compile("sqlite3");
32
33    Build::new(location)
34}
35
36/// The output of [`Build`], including the [`Location`] SQLite was built into.
37#[derive(PartialEq, Eq, Clone, Debug)]
38pub struct Build {
39    location: Location,
40}
41
42impl Build {
43    const fn new(location: Location) -> Build {
44        Self { location }
45    }
46
47    /// The `.c` source files that need to be built (`sqlite3.c`).
48    pub fn sources(&self) -> impl Iterator<Item = PathBuf> {
49        iter::once(self.input())
50    }
51
52    /// The build [`Location`].
53    pub const fn location(&self) -> &Location {
54        &self.location
55    }
56}
57
58impl Deref for Build {
59    type Target = Location;
60
61    fn deref(&self) -> &Self::Target {
62        self.location()
63    }
64}
65
66/// Specifies the source and target directories for [`build`].
67#[derive(PartialEq, Eq, Clone, Debug)]
68pub struct Location {
69    src: PathBuf,
70    dest: PathBuf,
71}
72
73impl Location {
74    /// Create a build [`Location`] from `$CARGO_MANIFEST_DIR`.
75    pub fn new(dest: impl Into<PathBuf>) -> Self {
76        Self {
77            src: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sqlite"),
78            dest: dest.into(),
79        }
80    }
81
82    /// The path to `sqlite3.c`.
83    pub fn input(&self) -> PathBuf {
84        self.src.join("sqlite3.c")
85    }
86
87    /// The path to `sqlite3.h`.
88    pub fn header(&self) -> PathBuf {
89        self.src.join("sqlite3.h")
90    }
91
92    /// The build's target directory.
93    pub fn dest(&self) -> PathBuf {
94        self.dest.clone()
95    }
96
97    /// Iterates source files (`sqlite3.c` and `sqlite3.h`).
98    pub fn sources(&self) -> impl Iterator<Item = PathBuf> {
99        iter::once(self.input()).chain(iter::once(self.header()))
100    }
101}
102
103impl Default for Location {
104    fn default() -> Self {
105        Self::new(PathBuf::from(
106            env::var_os("OUT_DIR").expect("$OUT_DIR not set"),
107        ))
108    }
109}
110
111/// Configures a SQLite [`build`], applying various [compile-time options][].
112///
113/// [compile-time options]: https://sqlite.org/compile.html
114#[derive(PartialEq, Eq, Clone, Debug)]
115pub struct Config {
116    settings: HashMap<SettingKey, Setting>,
117}
118
119impl Config {
120    /// Configure a build using the given [settings](Setting).
121    pub fn new(settings: impl IntoIterator<Item = Setting>) -> Self {
122        Self {
123            settings: settings
124                .into_iter()
125                .map(|setting| (setting.discriminant(), setting))
126                .collect(),
127        }
128    }
129
130    /// Look up a [setting](Setting) by its [key](SettingKey).
131    pub fn get(&self, key: SettingKey) -> Option<Setting> {
132        self.settings.get(&key).copied()
133    }
134
135    /// Change a build [setting](Setting).
136    pub fn set(&mut self, setting: Setting) {
137        self.settings.insert(setting.discriminant(), setting);
138    }
139
140    fn apply(&self, build: &mut cc::Build) {
141        for setting in self.settings.values() {
142            setting.apply(build);
143        }
144    }
145}
146
147impl AsRef<Config> for Config {
148    fn as_ref(&self) -> &Config {
149        self
150    }
151}
152
153impl Default for Config {
154    fn default() -> Self {
155        Self::new([
156            Setting::Sync(Synchronous::Full),
157            Setting::WalSync(Synchronous::Normal),
158            Setting::Threading(Threading::MultiThread),
159            Setting::DoubleQuotedStrings(DoubleQuotedStrings::default()),
160            Setting::DefaultForeignKeys(true),
161            Setting::DefaultMemoryStatus(false),
162            Setting::EnableAlloca(true),
163            Setting::EnableAuthorization(false),
164            Setting::EnableAutomaticIndex(true),
165            Setting::EnableAutomaticInitialize(true), // TODO
166            Setting::EnableAutomaticReset(false),
167            Setting::EnableBlobIo(false),
168            Setting::EnableColumnDeclaredType(false),
169            Setting::EnableDatabasePagesVirtualTable(false),
170            Setting::EnableDatabaseStatisticsVirtualTable(false),
171            Setting::EnableDatabaseUri(true),
172            Setting::EnableDeprecated(false),
173            Setting::EnableGetTable(false),
174            Setting::EnableMemoryManagement(true),
175            Setting::EnableProgressCallback(false),
176            Setting::EnableSharedCache(false),
177            Setting::EnableTrace(false),
178            Setting::EnableUtf16(false),
179            Setting::EnableVirtualTables(true),
180            Setting::EnableWriteAheadLog(true),
181            Setting::LikeOperatorMatchesBlob(false),
182            Setting::MaxExpressionDepth(0),
183            #[cfg(debug_assertions)]
184            Setting::EnableApiArmor(true),
185            #[cfg(debug_assertions)]
186            Setting::Debug(true),
187        ])
188    }
189}
190
191/// A [compile-time option][] for a SQLite [`build`].
192///
193/// [compile-time option]: https://sqlite.org/compile.html
194#[derive(EnumDiscriminants, PartialEq, Eq, Clone, Copy, Debug)]
195#[strum_discriminants(name(SettingKey))]
196#[strum_discriminants(derive(Hash))]
197pub enum Setting {
198    #[doc(alias = "SQLITE_DQS")]
199    DoubleQuotedStrings(DoubleQuotedStrings),
200    #[doc(alias = "SQLITE_THREADSAFE")]
201    Threading(Threading),
202    #[doc(alias = "SQLITE_DEBUG")]
203    Debug(bool),
204    #[doc(alias = "SQLITE_DEFAULT_SYNCHRONOUS")]
205    Sync(Synchronous),
206    #[doc(alias = "SQLITE_DEFAULT_WAL_SYNCHRONOUS")]
207    WalSync(Synchronous),
208    #[doc(alias = "SQLITE_DEFAULT_AUTOMATIC_INDEX")]
209    DefaultAutomaticIndex(bool),
210    #[doc(alias = "SQLITE_DEFAULT_AUTOVACUUM")]
211    DefaultAutomaticVacuum(bool),
212    #[doc(alias = "SQLITE_DEFAULT_FOREIGN_KEYS")]
213    DefaultForeignKeys(bool),
214    #[doc(alias = "SQLITE_DEFAULT_MEMSTATUS")]
215    DefaultMemoryStatus(bool),
216    #[doc(alias = "SQLITE_USE_ALLOCA")]
217    EnableAlloca(bool),
218    #[doc(alias = "SQLITE_ENABLE_API_ARMOR")]
219    EnableApiArmor(bool),
220    #[doc(alias = "SQLITE_OMIT_AUTHORIZATION")]
221    EnableAuthorization(bool),
222    #[doc(alias = "SQLITE_OMIT_AUTOMATIC_INDEX")]
223    EnableAutomaticIndex(bool),
224    #[doc(alias = "SQLITE_OMIT_AUTOINIT")]
225    EnableAutomaticInitialize(bool),
226    #[doc(alias = "SQLITE_OMIT_AUTORESET")]
227    EnableAutomaticReset(bool),
228    #[doc(alias = "SQLITE_OMIT_INCRBLOB")]
229    EnableBlobIo(bool),
230    #[doc(alias = "SQLITE_OMIT_DECLTYPE")]
231    EnableColumnDeclaredType(bool),
232    #[doc(alias = "SQLITE_ENABLE_COLUMN_METADATA")]
233    EnableColumnMetadata(bool),
234    #[doc(alias = "SQLITE_ENABLE_DBPAGE_VTAB")]
235    EnableDatabasePagesVirtualTable(bool),
236    #[doc(alias = "SQLITE_ENABLE_DBSTAT_VTAB")]
237    EnableDatabaseStatisticsVirtualTable(bool),
238    #[doc(alias = "SQLITE_USE_URI")]
239    EnableDatabaseUri(bool),
240    #[doc(alias = "SQLITE_OMIT_DEPRECATED")]
241    EnableDeprecated(bool),
242    #[doc(alias = "SQLITE_ENABLE_GEOPOLY")]
243    EnableGeopoly(bool),
244    #[doc(alias = "SQLITE_OMIT_GET_TABLE")]
245    EnableGetTable(bool),
246    #[doc(alias = "SQLITE_ENABLE_FTS3")]
247    #[doc(alias = "SQLITE_ENABLE_FTS4")]
248    EnableFts3(bool),
249    #[doc(alias = "SQLITE_ENABLE_FTS5")]
250    EnableFts5(bool),
251    #[doc(alias = "SQLITE_ENABLE_JSON1")]
252    #[doc(alias = "SQLITE_OMIT_JSON")]
253    EnableJson(bool),
254    #[doc(alias = "SQLITE_OMIT_LOAD_EXTENSION")]
255    EnableLoadExtension(bool),
256    #[doc(alias = "SQLITE_ENABLE_MEMORY_MANAGEMENT")]
257    EnableMemoryManagement(bool),
258    #[doc(alias = "SQLITE_ENABLE_NORMALIZE")]
259    EnableNormalizeSql(bool),
260    #[doc(alias = "SQLITE_ENABLE_PREUPDATE_HOOK")]
261    EnablePreUpdateHook(bool),
262    #[doc(alias = "SQLITE_OMIT_PROGRESS_CALLBACK")]
263    EnableProgressCallback(bool),
264    #[doc(alias = "SQLITE_ENABLE_RTREE")]
265    EnableRtree(bool),
266    #[doc(alias = "SQLITE_ENABLE_STAT4")]
267    EnableStat4(bool),
268    #[doc(alias = "SQLITE_OMIT_DESERIALIZE")]
269    EnableSerialize(bool),
270    #[doc(alias = "SQLITE_ENABLE_SESSION")]
271    EnableSession(bool),
272    #[doc(alias = "SQLITE_ENABLE_SNAPSHOT")]
273    EnableSnapshot(bool),
274    #[doc(alias = "SQLITE_OMIT_SHARED_CACHE")]
275    EnableSharedCache(bool),
276    #[doc(alias = "SQLITE_SOUNDEX")]
277    EnableSoundex(bool),
278    #[doc(alias = "SQLITE_OMIT_TCL_VARIABLE")]
279    EnableTclVariables(bool),
280    #[doc(alias = "SQLITE_OMIT_TEMPDB")]
281    EnableTemporaryDatabase(bool),
282    #[doc(alias = "SQLITE_OMIT_TRACE")]
283    EnableTrace(bool),
284    #[doc(alias = "SQLITE_OMIT_UTF16")]
285    EnableUtf16(bool),
286    #[doc(alias = "SQLITE_OMIT_VIRTUALTABLE")]
287    EnableVirtualTables(bool),
288    #[doc(alias = "SQLITE_OMIT_VIRTUALTABLE")]
289    EnableWriteAheadLog(bool),
290    #[doc(alias = "SQLITE_OMIT_WAL")]
291    LikeOperatorCaseSensitive(bool),
292    #[doc(alias = "SQLITE_LIKE_DOESNT_MATCH_BLOBS")]
293    LikeOperatorMatchesBlob(bool),
294    #[doc(alias = "SQLITE_MAX_ATTACHED")]
295    MaxAttachedDatabases(usize),
296    #[doc(alias = "SQLITE_MAX_COLUMN")]
297    MaxColumns(usize),
298    #[doc(alias = "SQLITE_MAX_EXPR_DEPTH")]
299    MaxExpressionDepth(usize),
300    #[doc(alias = "SQLITE_JSON_MAX_DEPTH")]
301    MaxJsonDepth(usize),
302    #[doc(alias = "SQLITE_MAX_VARIABLE_NUMBER")]
303    MaxVariables(usize),
304    #[doc(alias = "SQLITE_SECURE_DELETE")]
305    SecureDelete(bool),
306    #[doc(alias = "SQLITE_TEMP_STORE")]
307    TemporaryStorage(TemporaryStorage),
308    #[doc(alias = "SQLITE_TRUSTED_SCHEMA")]
309    TrustedSchema(bool),
310}
311
312impl Setting {
313    fn apply(&self, build: &mut cc::Build) {
314        match *self {
315            Setting::Debug(enable) => {
316                self.define(build, "SQLITE_DEBUG", enable);
317            }
318            Setting::DefaultAutomaticIndex(enable) => {
319                self.set(build, "SQLITE_DEFAULT_AUTOMATIC_INDEX", enable);
320            }
321            Setting::DefaultAutomaticVacuum(enable) => {
322                self.set(build, "SQLITE_DEFAULT_AUTOVACUUM", enable);
323            }
324            Setting::DefaultForeignKeys(enable) => {
325                self.set(build, "SQLITE_DEFAULT_FOREIGN_KEYS", enable);
326            }
327            Setting::DefaultMemoryStatus(enable) => {
328                self.set(build, "SQLITE_DEFAULT_MEMSTATUS", enable);
329            }
330            Setting::DoubleQuotedStrings(DoubleQuotedStrings { in_ddl, in_dml }) => {
331                let value = match (in_ddl, in_dml) {
332                    (true, true) => 3,
333                    (true, false) => 2,
334                    (false, true) => 1,
335                    (false, false) => 0,
336                };
337
338                self.set(build, "SQLITE_DQS", value);
339            }
340            Setting::EnableAlloca(enable) => {
341                self.define(build, "SQLITE_USE_ALLOCA", enable);
342            }
343            Setting::EnableApiArmor(enable) => {
344                self.define(build, "SQLITE_ENABLE_API_ARMOR", enable);
345            }
346            Setting::EnableAuthorization(enable) => {
347                self.define(build, "SQLITE_OMIT_AUTHORIZATION", !enable);
348            }
349            Setting::EnableAutomaticIndex(enable) => {
350                self.define(build, "SQLITE_OMIT_AUTOMATIC_INDEX", !enable);
351            }
352            Setting::EnableAutomaticInitialize(enable) => {
353                self.define(build, "SQLITE_OMIT_AUTOINIT", !enable);
354            }
355            Setting::EnableAutomaticReset(enable) => {
356                self.define(build, "SQLITE_OMIT_AUTORESET", !enable);
357            }
358            Setting::EnableBlobIo(enable) => {
359                self.define(build, "SQLITE_OMIT_INCRBLOB", !enable);
360            }
361            Setting::EnableColumnDeclaredType(enable) => {
362                self.define(build, "SQLITE_OMIT_DECLTYPE", !enable);
363            }
364            Setting::EnableColumnMetadata(enable) => {
365                self.define(build, "SQLITE_ENABLE_COLUMN_METADATA", enable);
366            }
367            Setting::EnableDatabasePagesVirtualTable(enable) => {
368                self.define(build, "SQLITE_ENABLE_DBPAGE_VTAB", enable);
369            }
370            Setting::EnableDatabaseStatisticsVirtualTable(enable) => {
371                self.define(build, "SQLITE_ENABLE_DBSTAT_VTAB", enable);
372            }
373            Setting::EnableDatabaseUri(enable) => {
374                self.set(build, "SQLITE_USE_URI", enable);
375            }
376            Setting::EnableDeprecated(enable) => {
377                self.define(build, "SQLITE_OMIT_DEPRECATED", !enable);
378            }
379            Setting::EnableFts3(enable) => {
380                self.define(build, "SQLITE_ENABLE_FTS3", enable);
381                self.define(build, "SQLITE_ENABLE_FTS3_PARENTHESIS", enable);
382            }
383            Setting::EnableFts5(enable) => {
384                self.define(build, "SQLITE_ENABLE_FTS5", enable);
385            }
386            Setting::EnableGeopoly(enable) => {
387                self.define(build, "SQLITE_ENABLE_GEOPOLY", enable);
388            }
389            Setting::EnableGetTable(enable) => {
390                self.define(build, "SQLITE_OMIT_GET_TABLE", !enable);
391            }
392            Setting::EnableJson(enable) => {
393                self.define(build, "SQLITE_OMIT_JSON", !enable);
394            }
395            Setting::EnableLoadExtension(enable) => {
396                self.define(build, "SQLITE_OMIT_LOAD_EXTENSION", !enable);
397            }
398            Setting::EnableMemoryManagement(enable) => {
399                self.define(build, "SQLITE_ENABLE_MEMORY_MANAGEMENT", enable);
400            }
401            Setting::EnableNormalizeSql(enable) => {
402                self.define(build, "SQLITE_ENABLE_NORMALIZE", enable);
403            }
404            Setting::EnablePreUpdateHook(enable) => {
405                self.define(build, "SQLITE_ENABLE_PREUPDATE_HOOK", enable);
406            }
407            Setting::EnableProgressCallback(enable) => {
408                self.define(build, "SQLITE_OMIT_PROGRESS_CALLBACK", !enable);
409            }
410            Setting::EnableRtree(enable) => {
411                self.define(build, "SQLITE_ENABLE_RTREE", enable);
412            }
413            Setting::EnableSerialize(enable) => {
414                self.define(build, "SQLITE_OMIT_DESERIALIZE", !enable);
415            }
416            Setting::EnableSession(enable) => {
417                self.define(build, "SQLITE_ENABLE_SESSION", enable);
418            }
419            Setting::EnableSharedCache(enable) => {
420                self.define(build, "SQLITE_OMIT_SHARED_CACHE", !enable);
421            }
422            Setting::EnableSnapshot(enable) => {
423                self.define(build, "SQLITE_ENABLE_SNAPSHOT", enable);
424            }
425            Setting::EnableSoundex(enable) => {
426                self.define(build, "SQLITE_SOUNDEX", enable);
427            }
428            Setting::EnableStat4(enable) => {
429                self.define(build, "SQLITE_ENABLE_STAT4", enable);
430            }
431            Setting::EnableTclVariables(enable) => {
432                self.define(build, "SQLITE_OMIT_TCL_VARIABLE", !enable);
433            }
434            Setting::EnableTemporaryDatabase(enable) => {
435                self.define(build, "SQLITE_OMIT_TEMPDB", !enable);
436            }
437            Setting::EnableTrace(enable) => {
438                self.define(build, "SQLITE_OMIT_TRACE", !enable);
439            }
440            Setting::EnableUtf16(enable) => {
441                self.define(build, "SQLITE_OMIT_UTF16", !enable);
442            }
443            Setting::EnableVirtualTables(enable) => {
444                self.define(build, "SQLITE_OMIT_VIRTUALTABLE", !enable);
445            }
446            Setting::EnableWriteAheadLog(enable) => {
447                self.define(build, "SQLITE_OMIT_WAL", !enable);
448            }
449            Setting::LikeOperatorCaseSensitive(enable) => {
450                self.define(build, "SQLITE_CASE_SENSITIVE_LIKE", enable);
451            }
452            Setting::LikeOperatorMatchesBlob(enable) => {
453                self.define(build, "SQLITE_LIKE_DOESNT_MATCH_BLOBS", !enable);
454            }
455            Setting::MaxAttachedDatabases(max) => {
456                self.set(build, "SQLITE_MAX_ATTACHED", max);
457            }
458            Setting::MaxColumns(max) => {
459                self.set(build, "SQLITE_MAX_COLUMN", max);
460            }
461            Setting::MaxExpressionDepth(max) => {
462                self.set(build, "SQLITE_MAX_EXPR_DEPTH", max);
463            }
464            Setting::MaxJsonDepth(max) => {
465                self.set(build, "SQLITE_JSON_MAX_DEPTH", max);
466            }
467            Setting::MaxVariables(max) => {
468                self.set(build, "SQLITE_MAX_VARIABLE_NUMBER", max);
469            }
470            Setting::SecureDelete(enable) => {
471                self.define(build, "SQLITE_SECURE_DELETE", enable);
472            }
473            Setting::Sync(synchronous) => {
474                self.set(build, "SQLITE_DEFAULT_SYNCHRONOUS", synchronous);
475            }
476            Setting::Threading(threading) => {
477                self.set(build, "SQLITE_THREADSAFE", threading);
478            }
479            Setting::TemporaryStorage(mode) => {
480                self.set(build, "SQLITE_TEMP_STORE", mode);
481            }
482            Setting::TrustedSchema(enable) => {
483                self.set(build, "SQLITE_TRUSTED_SCHEMA", enable);
484            }
485            Setting::WalSync(synchronous) => {
486                self.set(build, "SQLITE_DEFAULT_WAL_SYNCHRONOUS", synchronous);
487            }
488        }
489    }
490
491    fn define(&self, build: &mut cc::Build, name: &'static str, enable: bool) {
492        if enable {
493            build.define(name, None);
494        }
495    }
496
497    fn set(&self, build: &mut cc::Build, name: &'static str, value: impl SettingValue) {
498        value.apply(build, name);
499    }
500}
501
502#[derive(PartialEq, Eq, Clone, Copy, Default, Debug)]
503pub struct DoubleQuotedStrings {
504    in_ddl: bool,
505    in_dml: bool,
506}
507
508#[derive(PartialEq, Eq, Clone, Copy, Debug)]
509#[repr(usize)]
510pub enum TemporaryStorage {
511    AlwaysFilesystem = 0,
512    DefaultFilesystem = 1,
513    DefaultMemory = 2,
514    AlwaysMemory = 3,
515}
516
517#[derive(PartialEq, Eq, Clone, Copy, Debug)]
518#[repr(usize)]
519pub enum Threading {
520    SingleThread = 0,
521    MultiThread = 1,
522    Serialized = 2,
523}
524
525#[derive(PartialEq, Eq, Clone, Copy, Debug)]
526#[repr(usize)]
527pub enum Synchronous {
528    Off = 0,
529    Normal = 1,
530    Full = 2,
531    Extra = 3,
532}
533
534trait SettingValue {
535    fn apply(&self, build: &mut cc::Build, name: &'static str);
536}
537
538impl SettingValue for bool {
539    fn apply(&self, build: &mut cc::Build, name: &'static str) {
540        let value = if *self { "1" } else { "0" };
541        build.define(name, value);
542    }
543}
544
545impl SettingValue for usize {
546    fn apply(&self, build: &mut cc::Build, name: &'static str) {
547        let value = self.to_string();
548        build.define(name, value.as_str());
549    }
550}
551
552impl SettingValue for TemporaryStorage {
553    fn apply(&self, build: &mut cc::Build, name: &'static str) {
554        (*self as usize).apply(build, name);
555    }
556}
557
558impl SettingValue for Threading {
559    fn apply(&self, build: &mut cc::Build, name: &'static str) {
560        (*self as usize).apply(build, name);
561    }
562}
563
564impl SettingValue for Synchronous {
565    fn apply(&self, build: &mut cc::Build, name: &'static str) {
566        (*self as usize).apply(build, name);
567    }
568}