Refactor entire codebase and rewrite documentation
This commit is contained in:
142
Sources/DataLiteCore/Docs.docc/Articles/SQLiteRows.md
Normal file
142
Sources/DataLiteCore/Docs.docc/Articles/SQLiteRows.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Working with SQLiteRow
|
||||
|
||||
Represent SQL rows and parameters with SQLiteRow.
|
||||
|
||||
``SQLiteRow`` is an ordered container for column/value pairs. It preserves insertion order—matching
|
||||
the schema when representing result sets—and provides helpers for column names, named parameters,
|
||||
and literal rendering.
|
||||
|
||||
## Creating Rows
|
||||
|
||||
Initialize a row with a dictionary literal or assign values incrementally through subscripting.
|
||||
Values can be ``SQLiteValue`` instances or any type convertible via ``SQLiteRepresentable``.
|
||||
|
||||
```swift
|
||||
var payload: SQLiteRow = [
|
||||
"username": .text("ada"),
|
||||
"email": "ada@example.com".sqliteValue,
|
||||
"is_admin": false.sqliteValue
|
||||
]
|
||||
|
||||
payload["last_login_at"] = Int64(Date().timeIntervalSince1970).sqliteValue
|
||||
```
|
||||
|
||||
``SQLiteRow/columns`` returns the ordered column names, and ``SQLiteRow/namedParameters`` provides
|
||||
matching tokens (prefixed with `:`) suitable for parameterized SQL.
|
||||
|
||||
```swift
|
||||
print(payload.columns) // ["username", "email", "is_admin", "last_login_at"]
|
||||
print(payload.namedParameters) // [":username", ":email", ":is_admin", ":last_login_at"]
|
||||
```
|
||||
|
||||
## Generating SQL Fragments
|
||||
|
||||
Use row metadata to build SQL snippets without manual string concatenation:
|
||||
|
||||
```swift
|
||||
let columns = payload.columns.joined(separator: ", ")
|
||||
let placeholders = payload.namedParameters.joined(separator: ", ")
|
||||
let assignments = zip(payload.columns, payload.namedParameters)
|
||||
.map { "\($0) = \($1)" }
|
||||
.joined(separator: ", ")
|
||||
|
||||
// columns -> "username, email, is_admin, last_login_at"
|
||||
// placeholders -> ":username, :email, :is_admin, :last_login_at"
|
||||
// assignments -> "username = :username, ..."
|
||||
```
|
||||
|
||||
When generating migrations or inserting literal values, ``SQLiteValue/sqliteLiteral`` renders safe
|
||||
SQL fragments for numeric and text values. Always escape identifiers manually if column names come
|
||||
from untrusted input.
|
||||
|
||||
## Inserting Rows
|
||||
|
||||
Bind an entire row to a statement using ``StatementProtocol/bind(_:)``. The method matches column
|
||||
names to identically named placeholders.
|
||||
|
||||
```swift
|
||||
var user: SQLiteRow = [
|
||||
"username": .text("ada"),
|
||||
"email": .text("ada@example.com"),
|
||||
"created_at": Int64(Date().timeIntervalSince1970).sqliteValue
|
||||
]
|
||||
|
||||
let insertSQL = """
|
||||
INSERT INTO users (\(user.columns.joined(separator: ", ")))
|
||||
VALUES (\(user.namedParameters.joined(separator: ", ")))
|
||||
"""
|
||||
|
||||
let insert = try connection.prepare(sql: insertSQL)
|
||||
try insert.bind(user)
|
||||
try insert.step()
|
||||
try insert.reset()
|
||||
```
|
||||
|
||||
To insert multiple rows, prepare an array of ``SQLiteRow`` values and call
|
||||
``StatementProtocol/execute(_:)``. The helper performs binding, stepping, and clearing for each row:
|
||||
|
||||
```swift
|
||||
let batch: [SQLiteRow] = [
|
||||
["username": .text("ada"), "email": .text("ada@example.com")],
|
||||
["username": .text("grace"), "email": .text("grace@example.com")]
|
||||
]
|
||||
|
||||
try insert.execute(batch)
|
||||
```
|
||||
|
||||
## Updating Rows
|
||||
|
||||
Because ``SQLiteRow`` is a value type, you can duplicate and extend it for related operations such
|
||||
as building `SET` clauses or constructing `WHERE` conditions.
|
||||
|
||||
```swift
|
||||
var changes: SQLiteRow = [
|
||||
"email": .text("ada@new.example"),
|
||||
"last_login_at": Int64(Date().timeIntervalSince1970).sqliteValue
|
||||
]
|
||||
|
||||
let setClause = zip(changes.columns, changes.namedParameters)
|
||||
.map { "\($0) = \($1)" }
|
||||
.joined(separator: ", ")
|
||||
|
||||
var parameters = changes
|
||||
parameters["id"] = .int(1)
|
||||
|
||||
let update = try connection.prepare(sql: """
|
||||
UPDATE users
|
||||
SET \(setClause)
|
||||
WHERE id = :id
|
||||
""")
|
||||
|
||||
try update.bind(parameters)
|
||||
try update.step()
|
||||
```
|
||||
|
||||
## Reading Rows
|
||||
|
||||
``StatementProtocol/currentRow()`` returns an ``SQLiteRow`` snapshot of the current result. Use it
|
||||
to pass data through mapping layers or transform results lazily without immediate conversion:
|
||||
|
||||
```swift
|
||||
let statement = try connection.prepare(sql: "SELECT id, email FROM users LIMIT 10")
|
||||
|
||||
var rows: [SQLiteRow] = []
|
||||
while try statement.step() {
|
||||
if let row = statement.currentRow() {
|
||||
rows.append(row)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can iterate over a row’s columns via `columns`, and subscript by name to retrieve stored values.
|
||||
For typed access, cast through ``SQLiteValue`` or adopt ``SQLiteRepresentable`` in your custom
|
||||
types.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Use ``SQLiteRow/description`` to log payloads during development. For security-sensitive logs,
|
||||
redact or whitelist keys before printing. Because rows preserve order, logs mirror the schema
|
||||
defined in your SQL, making comparisons straightforward.
|
||||
|
||||
- SeeAlso: ``SQLiteRow``
|
||||
- SeeAlso: ``StatementProtocol``
|
||||
Reference in New Issue
Block a user