|
Lightweight 0.20260625.0
|
To connect to the database you need to provide connection string that library uses to establish connection and you can check if it is alive in the following way
To directly make a call to the database use ExecuteDirect function, for example
Classic per-row fetch loops like the one above issue one SQLFetch per row, i.e. one network round-trip per row. On TCP-backed drivers (Microsoft SQL Server, PostgreSQL) that latency dominates the wall-clock time of large result sets.
Lightweight transparently reduces these round-trips: on the first FetchRow() of a result set it inspects the columns and, when eligible, fetches whole blocks of rows per SQLFetchScroll round-trip (ODBC row-array binding) and serves your FetchRow() / GetColumn<T>() calls from that buffer. No code change is required — the loops above, SqlRowIterator<T>, SqlVariantRowCursor and the DataMapper all benefit automatically.
The depth is a connection-level setting (default Lightweight::PrefetchDepthDefault, 1000 rows). A value <= 1 disables prefetch and restores one SQLFetch per row:
Prefetch engages only for result sets whose columns are fixed-width numeric, temporal, or GUID types (integers, floating point, DATE, TIMESTAMP/DATETIME, and native GUID/uniqueidentifier/ uuid) on drivers that support native row-array fetching (Microsoft SQL Server, PostgreSQL, SQLite). Result sets that contain character/text, NUMERIC/DECIMAL, TIME, binary or LOB columns transparently keep the per-row path: faithful block reconstruction of those is not achievable uniformly across backends (e.g. Microsoft SQL Server returns narrow text in the client codepage rather than UTF-8, and SQLite's dynamic typing reports text/NUMERIC columns with an unreliable, unenforced size), so the dedicated single-row binders handle them. Memory is bounded to a few MB per active cursor (the depth is auto-clamped to that budget), and prefetch reads ahead up to one block, so a loop that stops early over-reads at most one block.
You can also use prepared statements to execute queries, for example
Or construct statement using SqlQueryBuilder
For more info see SqlQuery and SqlQueryFormatter documentation
The DataMapper provides a higher-level abstraction for interacting with databases. It simplifies operations by automatically creating tables based on the specified type and enabling data retrieval through straightforward method calls. For more info see DataMapper documentation
To insert or update many records efficiently, use CreateAll and UpdateAll. They prepare a single statement once and submit the whole batch, preferring native ODBC row-wise array binding (one SQLExecute, zero-copy) when every column is a fixed-width type — primitives, SqlDate/SqlTime/ SqlDateTime, SqlNumeric, inline fixed-capacity strings (SqlAnsiString/SqlFixedString), or std::optional of a fixed non-numeric type (including nullable fixed-capacity strings) — and the driver supports parameter arrays. Records with variable-length columns (e.g. std::string) transparently fall back to a prepare-once + per-row execute, which is still far cheaper than calling Create/CreateExplicit in a loop (those re-prepare per row).
Note:
CreateAll/UpdateAlldo not write primary keys, relations, or modified-state back onto the records (treat them as write-only inputs), andUpdateAllwrites a uniform set of columns for every row rather than only the per-record modified ones. The range must be contiguous.
Accessing a relation on a query result loads it on demand — one query per record. Over a result set of N records that is the N+1 problem: reading album.tracks for 1000 albums issues 1001 queries. With<&Record::relation>() instead resolves the relation for the whole result set once it has been materialized, using WHERE <key> IN (...):
BelongsTo and HasMany. HasOneThrough, HasManyThrough and CompositeForeignKey still load on demand; naming one of them in With<>() is a compile error rather than a silent fallback.All(), First(), First(n) and Range().IN predicate is chunked (see SqlQueryFormatter::MaxInPredicateValues, 1000 by default), so a large batch costs one query per chunk — a constant number of queries per relation, never one per record.BelongsTo whose foreign key is NULL, and an owner with no children, are handled without an extra query: the childless owner's relation is marked loaded-and-empty rather than left to query for a result already known.With<>() with DataMapperOptions { .loadRelations = false } therefore turns any unrequested relation access into a SqlRequireLoadedError instead of a silent query — useful to prove a code path issues no N+1.Eager-loading one level is not enough for a chain. Every record holds its own copy of its BelongsTo target, so reaching a relation of that copy runs the copy's own lazy loader — the N+1 simply moves one level down. Name the whole path instead:
Three queries in total, for any number of tracks. Each level is resolved for every record reached by the level above it, at once. A path may also run through the "many" side (.With<&Album::tracks, &Track::genre>()): the middle level fans out, and the level below it is still one query rather than one per child.
Already-loaded relations are skipped, so overlapping paths (.With<&A::b>() next to .With<&A::b, &B::c>()) do not fetch b twice.
When a whole object graph is wanted rather than named paths, set a depth on the query instead:
eagerLoadDepth batch-loads every BelongsTo and HasMany reachable within that many levels. Prefer With<>() when only part of the graph is needed: the depth walk fetches more rows, and instantiates the loader for the whole reachable relation graph, which costs compile time. The depth is what bounds both — and what lets a cyclic graph (a self-referencing record, or A → B → A) terminate, since the recursion is cut at a compile-time constant.
Measured on 1000 owners with 10 children each, comparing the on-demand path with With<>():
| relation | queries before | queries after | SQLite3 | PostgreSQL | MS SQL Server |
|---|---|---|---|---|---|
HasMany | 1001 | 2 | 8.7x | 45x | 45x |
BelongsTo | 10001 | 2 | 37x | 464x | 407x |
When only read access is needed, you can use a simple struct to represent the row, and also do not need to wrap the fields into Field<> template. The struct must have fields that match the columns in the query. The fields can be of any type that can be converted from the column type. The struct can have more fields than the columns in the query, but the fields that match the columns must be in the same order as the columns in the query.