Compare commits
36 Commits
d3786253ef
...
83db282892
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83db282892 | ||
|
|
f5d5196bf5 | ||
|
|
7ec869baa8 | ||
|
|
1a257b3057 | ||
|
|
b9fd87ed7c | ||
|
|
fda763d3f0 | ||
|
|
77d89b17e8 | ||
|
|
df0fa46591 | ||
|
|
1f5d70ce0d | ||
|
|
0d46c86469 | ||
|
|
a439f8fdcb | ||
|
|
1adddf4e4c | ||
|
|
23484716e2 | ||
|
|
92085f9071 | ||
|
|
ce899b569e | ||
|
|
da7216630b | ||
|
|
b57c270144 | ||
|
|
230b8fdc35 | ||
|
|
75b2446801 | ||
|
|
96779c9caf | ||
|
|
bf5d9ff6b1 | ||
|
|
c551faeea3 | ||
|
|
3f38f90a50 | ||
|
|
26a1328c89 | ||
|
|
21b45b8dd7 | ||
|
|
3a860bd2d5 | ||
|
|
c2c95c18b4 | ||
|
|
e3a301009b | ||
|
|
c90bdc8907 | ||
|
|
dba12a38c2 | ||
|
|
4b60452b27 | ||
|
|
d2f677ae0c | ||
|
|
427bb6929a | ||
|
|
78c04ee576 | ||
|
|
3309d26142 | ||
|
|
5a4a2aaa17 |
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: "Upgrade libsqlite3-sys"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Refactor 260: Upgrade libsqlite3-sys
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Upgrade the `libsqlite3-sys` dependency from `0.35.0` to `0.37.0`. The crate is used with `features = ["bundled"]` for static builds.
|
||||||
|
|
||||||
|
## Version Notes
|
||||||
|
|
||||||
|
- Current: `libsqlite3-sys 0.35.0` (pinned transitively by `matrix-sdk 0.16.0` → `matrix-sdk-sqlite` → `rusqlite 0.37.x`)
|
||||||
|
- Target: `libsqlite3-sys 0.37.0`
|
||||||
|
- Latest upstream rusqlite: `0.39.0`
|
||||||
|
- **Blocker**: `matrix-sdk 0.16.0` pins `rusqlite 0.37.x` which pins `libsqlite3-sys 0.35.0`. A clean upgrade requires either waiting for matrix-sdk to bump their rusqlite dep, or upgrading matrix-sdk itself.
|
||||||
|
- **Reverted 2026-03-17**: A previous coder vendored the entire rusqlite crate with a fake `0.37.99` version and patched its libsqlite3-sys dep. This was too hacky — reverted to clean `0.35.0`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `libsqlite3-sys` is upgraded to `0.37.0` via a clean dependency path (no vendored forks)
|
||||||
|
- [ ] `cargo build` succeeds
|
||||||
|
- [ ] All tests pass
|
||||||
|
- [ ] No `[patch.crates-io]` hacks or vendored crates
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
name: "Spikes skip merge and stop for human review"
|
||||||
|
agent: coder-opus
|
||||||
|
---
|
||||||
|
|
||||||
|
# Story 265: Spikes skip merge and stop for human review
|
||||||
|
|
||||||
|
## User Story
|
||||||
|
|
||||||
|
As a user, I want spike work items to stop after QA instead of auto-advancing to the merge stage, so that I can review the spike's findings and prototype code in the worktree before deciding what to do with them.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Spikes are investigative — their value is the findings and any prototype code, not a merge to master. The user needs to:
|
||||||
|
- Read the spike document with findings
|
||||||
|
- Review prototype code in the worktree
|
||||||
|
- Optionally build and run the prototype to validate the approach
|
||||||
|
- Then manually decide: archive the spike and create follow-up stories, or reject and re-investigate
|
||||||
|
|
||||||
|
Currently all work items follow the same pipeline: coder → QA → merge → done. Spikes should diverge after QA and wait for human review instead of auto-advancing to merge.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] Items with `_spike_` in the filename skip the merge stage after QA passes
|
||||||
|
- [ ] After QA, spike items remain accessible for human review (worktree preserved, not cleaned up)
|
||||||
|
- [ ] Spikes do not auto-advance to `4_merge/` — they stay in `3_qa/` or move to a review-hold state
|
||||||
|
- [ ] The human can manually archive the spike when done reviewing
|
||||||
|
- [ ] Non-spike items (stories, bugs, refactors) continue through the full pipeline as before
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- New UI for spike review (manual file inspection is fine)
|
||||||
|
- Changes to the spike creation flow
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: "Upgrade libsqlite3-sys"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Refactor 260: Upgrade libsqlite3-sys
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Upgrade the `libsqlite3-sys` dependency from `0.35.0` to `0.37.0`. The crate is used with `features = ["bundled"]` for static builds.
|
|
||||||
|
|
||||||
A previous manual attempt to upgrade failed on compile, so the agent should investigate compatibility with our current `matrix-sdk-sqlite` dependency and resolve any breaking changes.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- [ ] `libsqlite3-sys` is upgraded to `0.37.0`
|
|
||||||
- [ ] `cargo build` succeeds
|
|
||||||
- [ ] All tests pass
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
name: "Matrix bot self-signs device keys at startup for verified encryption"
|
||||||
|
agent: mergemaster
|
||||||
|
---
|
||||||
|
|
||||||
|
# Story 263: Matrix bot self-signs device keys at startup for verified encryption
|
||||||
|
|
||||||
|
## User Story
|
||||||
|
|
||||||
|
As a Matrix room participant, I want the bot's messages to not show "encrypted by a device not verified by its owner" warnings, so that I have confidence the bot's encryption is fully verified.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] At startup the bot checks whether its own device keys have been self-signed (cross-signed by its own user identity)
|
||||||
|
- [ ] If the device keys are not self-signed, the bot signs them automatically
|
||||||
|
- [ ] After signing, the bot uploads the new signatures to the homeserver
|
||||||
|
- [ ] After a clean start (fresh matrix_store / device_id) the bot's messages no longer show the 'encrypted by a device not verified by its owner' warning
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- TBD
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
name: "Claude Code session ID not persisted across browser refresh"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bug 264: Claude Code session ID not persisted across browser refresh
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
The Claude Code provider uses a session_id to resume conversations via `--resume <id>`. This session_id is stored in React state (`claudeSessionId`) but is NOT persisted to localStorage. After a browser refresh, the session_id is lost (`null`), so Claude Code cannot resume the prior session.
|
||||||
|
|
||||||
|
A fallback exists (`build_claude_code_context_prompt` in `server/src/llm/chat.rs:188`) that injects prior messages as flattened text inside a `<conversation_history>` block, but this loses structure (tool calls, tool results, reasoning) and Claude Code treats it as informational text rather than actual conversation turns. In practice, the LLM does not retain meaningful context after refresh.
|
||||||
|
|
||||||
|
This is the root cause behind bug 245 (chat history persistence regression). The localStorage message persistence from story 145 works correctly for the UI, but the LLM context is not properly restored because the session cannot be resumed.
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- `frontend/src/components/Chat.tsx:174` — `claudeSessionId` is ephemeral React state
|
||||||
|
- `frontend/src/components/Chat.tsx:553` — session_id only sent when non-null
|
||||||
|
- `server/src/llm/chat.rs:278` — backend branches on session_id presence
|
||||||
|
- `server/src/llm/providers/claude_code.rs:44` — `--resume` flag passed to Claude CLI
|
||||||
|
|
||||||
|
## How to Reproduce
|
||||||
|
|
||||||
|
1. Open the Story Kit web UI and select claude-code-pty as the model
|
||||||
|
2. Have a multi-turn conversation with the agent
|
||||||
|
3. Refresh the browser (F5 or Cmd+R)
|
||||||
|
4. Send a new message referencing the prior conversation
|
||||||
|
5. The LLM has no knowledge of the prior conversation
|
||||||
|
|
||||||
|
## Actual Result
|
||||||
|
|
||||||
|
After refresh, claudeSessionId is null. Claude Code spawns a fresh session without --resume. The fallback text injection is too lossy to provide meaningful context. The LLM behaves as if the conversation never happened.
|
||||||
|
|
||||||
|
## Expected Result
|
||||||
|
|
||||||
|
After refresh, the Claude Code session is resumed via --resume, giving the LLM full context of the prior conversation including tool calls, reasoning, and all turns.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] claudeSessionId is persisted to localStorage (scoped by project path) and restored on component mount
|
||||||
|
- [ ] After browser refresh, the next chat message includes session_id in the ProviderConfig
|
||||||
|
- [ ] Claude Code receives --resume with the persisted session_id after refresh
|
||||||
|
- [ ] Clearing the session (clear button) also clears the persisted session_id
|
||||||
|
- [ ] After server restart with session files intact on disk, conversation resumes correctly
|
||||||
49
Cargo.lock
generated
49
Cargo.lock
generated
@@ -1048,12 +1048,6 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -1313,7 +1307,7 @@ version = "0.15.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foldhash 0.1.5",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1321,17 +1315,14 @@ name = "hashbrown"
|
|||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
dependencies = [
|
|
||||||
"foldhash 0.2.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
|
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.15.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1957,9 +1948,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.37.0"
|
version = "0.35.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
|
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
@@ -3329,16 +3320,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rsqlite-vfs"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -3516,7 +3497,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.37.99"
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
@@ -3524,7 +3507,6 @@ dependencies = [
|
|||||||
"hashlink",
|
"hashlink",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlite-wasm-rs",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3995,18 +3977,6 @@ dependencies = [
|
|||||||
"der",
|
"der",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlite-wasm-rs"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"js-sys",
|
|
||||||
"rsqlite-vfs",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sse-codec"
|
name = "sse-codec"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -4048,7 +4018,6 @@ dependencies = [
|
|||||||
"portable-pty",
|
"portable-pty",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"reqwest 0.13.2",
|
"reqwest 0.13.2",
|
||||||
"rusqlite",
|
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -37,9 +37,3 @@ matrix-sdk = { version = "0.16.0", default-features = false, features = [
|
|||||||
pulldown-cmark = { version = "0.13.1", default-features = false, features = [
|
pulldown-cmark = { version = "0.13.1", default-features = false, features = [
|
||||||
"html",
|
"html",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
# Patch rusqlite 0.37.x (used by matrix-sdk-sqlite) with a local fork that requires
|
|
||||||
# libsqlite3-sys 0.37.0 instead of 0.35.0, enabling a single unified libsqlite3-sys
|
|
||||||
# 0.37.0 in the dependency graph with the "bundled" feature for static builds.
|
|
||||||
rusqlite = { path = "vendor/rusqlite" }
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ type WsHandlers = {
|
|||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
let capturedWsHandlers: WsHandlers | null = null;
|
let capturedWsHandlers: WsHandlers | null = null;
|
||||||
|
// Captures the last sendChat call's arguments for assertion.
|
||||||
|
let lastSendChatArgs: { messages: Message[]; config: unknown } | null = null;
|
||||||
|
|
||||||
vi.mock("../api/client", () => {
|
vi.mock("../api/client", () => {
|
||||||
const api = {
|
const api = {
|
||||||
@@ -42,7 +44,9 @@ vi.mock("../api/client", () => {
|
|||||||
capturedWsHandlers = handlers;
|
capturedWsHandlers = handlers;
|
||||||
}
|
}
|
||||||
close() {}
|
close() {}
|
||||||
sendChat() {}
|
sendChat(messages: Message[], config: unknown) {
|
||||||
|
lastSendChatArgs = { messages, config };
|
||||||
|
}
|
||||||
cancel() {}
|
cancel() {}
|
||||||
}
|
}
|
||||||
return { api, ChatWebSocket };
|
return { api, ChatWebSocket };
|
||||||
@@ -580,6 +584,62 @@ describe("Chat localStorage persistence (Story 145)", () => {
|
|||||||
expect(storedAfterRemount).toEqual(history);
|
expect(storedAfterRemount).toEqual(history);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Bug 245: after refresh, sendChat includes full prior history", async () => {
|
||||||
|
// Step 1: Render, populate messages via onUpdate, then unmount (simulate refresh)
|
||||||
|
const { unmount } = render(
|
||||||
|
<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />,
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
|
||||||
|
const priorHistory: Message[] = [
|
||||||
|
{ role: "user", content: "What is Rust?" },
|
||||||
|
{ role: "assistant", content: "Rust is a systems programming language." },
|
||||||
|
];
|
||||||
|
act(() => {
|
||||||
|
capturedWsHandlers?.onUpdate(priorHistory);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify localStorage has the prior history
|
||||||
|
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
|
||||||
|
expect(stored).toEqual(priorHistory);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
// Step 2: Remount (simulates page reload) — messages load from localStorage
|
||||||
|
capturedWsHandlers = null;
|
||||||
|
lastSendChatArgs = null;
|
||||||
|
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
|
||||||
|
// Verify prior messages are displayed
|
||||||
|
expect(await screen.findByText("What is Rust?")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Step 3: Send a new message — sendChat should include the full prior history
|
||||||
|
const input = screen.getByPlaceholderText("Send a message...");
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.change(input, { target: { value: "Tell me more" } });
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify sendChat was called with ALL prior messages + the new one
|
||||||
|
expect(lastSendChatArgs).not.toBeNull();
|
||||||
|
expect(lastSendChatArgs?.messages).toHaveLength(3);
|
||||||
|
expect(lastSendChatArgs?.messages[0]).toEqual({
|
||||||
|
role: "user",
|
||||||
|
content: "What is Rust?",
|
||||||
|
});
|
||||||
|
expect(lastSendChatArgs?.messages[1]).toEqual({
|
||||||
|
role: "assistant",
|
||||||
|
content: "Rust is a systems programming language.",
|
||||||
|
});
|
||||||
|
expect(lastSendChatArgs?.messages[2]).toEqual({
|
||||||
|
role: "user",
|
||||||
|
content: "Tell me more",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("AC5: uses project-scoped storage key", async () => {
|
it("AC5: uses project-scoped storage key", async () => {
|
||||||
const otherKey = "storykit-chat-history:/other/project";
|
const otherKey = "storykit-chat-history:/other/project";
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@@ -1215,3 +1275,114 @@ describe("Remove bubble styling from streaming messages (Story 163)", () => {
|
|||||||
expect(styleAttr).not.toContain("background: transparent");
|
expect(styleAttr).not.toContain("background: transparent");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Bug 264: Claude Code session ID persisted across browser refresh", () => {
|
||||||
|
const PROJECT_PATH = "/tmp/project";
|
||||||
|
const SESSION_KEY = `storykit-claude-session-id:${PROJECT_PATH}`;
|
||||||
|
const STORAGE_KEY = `storykit-chat-history:${PROJECT_PATH}`;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
capturedWsHandlers = null;
|
||||||
|
lastSendChatArgs = null;
|
||||||
|
localStorage.clear();
|
||||||
|
setupMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("AC1: session_id is persisted to localStorage when onSessionId fires", async () => {
|
||||||
|
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||||
|
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedWsHandlers?.onSessionId("test-session-abc");
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(localStorage.getItem(SESSION_KEY)).toBe("test-session-abc");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("AC2: after remount, next sendChat includes session_id from localStorage", async () => {
|
||||||
|
// Step 1: Render, receive a session ID, then unmount (simulate refresh)
|
||||||
|
localStorage.setItem(SESSION_KEY, "persisted-session-xyz");
|
||||||
|
localStorage.setItem(
|
||||||
|
STORAGE_KEY,
|
||||||
|
JSON.stringify([
|
||||||
|
{ role: "user", content: "Prior message" },
|
||||||
|
{ role: "assistant", content: "Prior reply" },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />,
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
// Step 2: Remount (simulates page reload)
|
||||||
|
capturedWsHandlers = null;
|
||||||
|
lastSendChatArgs = null;
|
||||||
|
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
|
||||||
|
// Prior messages should be visible
|
||||||
|
expect(await screen.findByText("Prior message")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Step 3: Send a new message — config should include session_id
|
||||||
|
const input = screen.getByPlaceholderText("Send a message...");
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.change(input, { target: { value: "Continue" } });
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(lastSendChatArgs).not.toBeNull();
|
||||||
|
expect(
|
||||||
|
(lastSendChatArgs?.config as Record<string, unknown>).session_id,
|
||||||
|
).toBe("persisted-session-xyz");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("AC3: clearing the session also clears the persisted session_id", async () => {
|
||||||
|
localStorage.setItem(SESSION_KEY, "session-to-clear");
|
||||||
|
|
||||||
|
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);
|
||||||
|
|
||||||
|
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||||
|
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
|
||||||
|
const newSessionBtn = screen.getByText(/New Session/);
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(newSessionBtn);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(localStorage.getItem(SESSION_KEY)).toBeNull();
|
||||||
|
|
||||||
|
confirmSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("AC1: storage key is scoped to project path", async () => {
|
||||||
|
const otherPath = "/other/project";
|
||||||
|
const otherKey = `storykit-claude-session-id:${otherPath}`;
|
||||||
|
localStorage.setItem(otherKey, "other-session");
|
||||||
|
|
||||||
|
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||||
|
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedWsHandlers?.onSessionId("my-session");
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(localStorage.getItem(SESSION_KEY)).toBe("my-session");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Other project's session should be untouched
|
||||||
|
expect(localStorage.getItem(otherKey)).toBe("other-session");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -171,7 +171,16 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
merge: [],
|
merge: [],
|
||||||
done: [],
|
done: [],
|
||||||
});
|
});
|
||||||
const [claudeSessionId, setClaudeSessionId] = useState<string | null>(null);
|
const [claudeSessionId, setClaudeSessionId] = useState<string | null>(() => {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
localStorage.getItem(`storykit-claude-session-id:${projectPath}`) ??
|
||||||
|
null
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
const [activityStatus, setActivityStatus] = useState<string | null>(null);
|
const [activityStatus, setActivityStatus] = useState<string | null>(null);
|
||||||
const [permissionQueue, setPermissionQueue] = useState<
|
const [permissionQueue, setPermissionQueue] = useState<
|
||||||
{
|
{
|
||||||
@@ -247,6 +256,21 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
};
|
};
|
||||||
}, [messages, streamingContent, model]);
|
}, [messages, streamingContent, model]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
if (claudeSessionId !== null) {
|
||||||
|
localStorage.setItem(
|
||||||
|
`storykit-claude-session-id:${projectPath}`,
|
||||||
|
claudeSessionId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(`storykit-claude-session-id:${projectPath}`);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore — quota or security errors.
|
||||||
|
}
|
||||||
|
}, [claudeSessionId, projectPath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api
|
api
|
||||||
.getOllamaModels()
|
.getOllamaModels()
|
||||||
@@ -664,6 +688,11 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
setActivityStatus(null);
|
setActivityStatus(null);
|
||||||
setClaudeSessionId(null);
|
setClaudeSessionId(null);
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(`storykit-claude-session-id:${projectPath}`);
|
||||||
|
} catch {
|
||||||
|
// Ignore — quota or security errors.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export default defineConfig(() => {
|
|||||||
timeout: 120000,
|
timeout: 120000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
ignored: ["**/.story_kit/**", "**/target/**"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "story-kit"
|
name = "story-kit"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
@@ -32,10 +32,7 @@ matrix-sdk = { workspace = true }
|
|||||||
pulldown-cmark = { workspace = true }
|
pulldown-cmark = { workspace = true }
|
||||||
|
|
||||||
# Force bundled SQLite so static musl builds don't need a system libsqlite3
|
# Force bundled SQLite so static musl builds don't need a system libsqlite3
|
||||||
libsqlite3-sys = { version = "0.37.0", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.35.0", features = ["bundled"] }
|
||||||
# Enable fallible_uint feature to restore u64/usize ToSql/FromSql impls needed
|
|
||||||
# by matrix-sdk-sqlite (removed in rusqlite 0.38+ without this feature flag)
|
|
||||||
rusqlite = { version = "0.37.99", features = ["fallible_uint"] }
|
|
||||||
wait-timeout = "0.2.1"
|
wait-timeout = "0.2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1788,6 +1788,13 @@ fn tool_report_merge_failure(args: &Value, ctx: &AppContext) -> Result<String, S
|
|||||||
slog!("[mergemaster] Merge failure reported for '{story_id}': {reason}");
|
slog!("[mergemaster] Merge failure reported for '{story_id}': {reason}");
|
||||||
ctx.agents.set_merge_failure_reported(story_id);
|
ctx.agents.set_merge_failure_reported(story_id);
|
||||||
|
|
||||||
|
// Broadcast the failure so the Matrix notification listener can post an
|
||||||
|
// error message to configured rooms without coupling this tool to the bot.
|
||||||
|
let _ = ctx.watcher_tx.send(crate::io::watcher::WatcherEvent::MergeFailure {
|
||||||
|
story_id: story_id.to_string(),
|
||||||
|
reason: reason.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
// Persist the failure reason to the story file's front matter so it
|
// Persist the failure reason to the story file's front matter so it
|
||||||
// survives server restarts and is visible in the web UI.
|
// survives server restarts and is visible in the web UI.
|
||||||
if let Ok(project_root) = ctx.state.get_project_root() {
|
if let Ok(project_root) = ctx.state.get_project_root() {
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ impl From<WatcherEvent> for Option<WsResponse> {
|
|||||||
}),
|
}),
|
||||||
WatcherEvent::ConfigChanged => Some(WsResponse::AgentConfigChanged),
|
WatcherEvent::ConfigChanged => Some(WsResponse::AgentConfigChanged),
|
||||||
WatcherEvent::AgentStateChanged => Some(WsResponse::AgentStateChanged),
|
WatcherEvent::AgentStateChanged => Some(WsResponse::AgentStateChanged),
|
||||||
|
// MergeFailure is handled by the Matrix notification listener only;
|
||||||
|
// no WebSocket message is needed for the frontend.
|
||||||
|
WatcherEvent::MergeFailure { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ pub enum WatcherEvent {
|
|||||||
/// Triggers a pipeline state refresh so the frontend can update agent
|
/// Triggers a pipeline state refresh so the frontend can update agent
|
||||||
/// assignments without waiting for a filesystem event.
|
/// assignments without waiting for a filesystem event.
|
||||||
AgentStateChanged,
|
AgentStateChanged,
|
||||||
|
/// A story encountered a failure (e.g. merge failure).
|
||||||
|
/// Triggers an error notification to configured Matrix rooms.
|
||||||
|
MergeFailure {
|
||||||
|
/// Work item ID (e.g. `"42_story_my_feature"`).
|
||||||
|
story_id: String,
|
||||||
|
/// Human-readable description of the failure.
|
||||||
|
reason: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `path` is the root-level `.story_kit/project.toml`, i.e.
|
/// Return `true` if `path` is the root-level `.story_kit/project.toml`, i.e.
|
||||||
|
|||||||
@@ -179,6 +179,44 @@ pub fn set_anthropic_api_key(store: &dyn StoreOps, api_key: String) -> Result<()
|
|||||||
set_anthropic_api_key_impl(store, &api_key)
|
set_anthropic_api_key_impl(store, &api_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a prompt for Claude Code that includes prior conversation history.
|
||||||
|
///
|
||||||
|
/// When a Claude Code session cannot be resumed (no session_id), we embed
|
||||||
|
/// the prior messages as a structured preamble so the LLM retains context.
|
||||||
|
/// If there is only one user message (the current one), the content is
|
||||||
|
/// returned as-is with no preamble.
|
||||||
|
fn build_claude_code_context_prompt(messages: &[Message], latest_user_content: &str) -> String {
|
||||||
|
// Collect prior messages (everything except the trailing user message).
|
||||||
|
let prior: Vec<&Message> = messages
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.skip(1) // skip the latest user message
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if prior.is_empty() {
|
||||||
|
return latest_user_content.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
parts.push("<conversation_history>".to_string());
|
||||||
|
for msg in &prior {
|
||||||
|
let label = match msg.role {
|
||||||
|
Role::User => "User",
|
||||||
|
Role::Assistant => "Assistant",
|
||||||
|
Role::Tool => "Tool",
|
||||||
|
Role::System => continue,
|
||||||
|
};
|
||||||
|
parts.push(format!("[{}]: {}", label, msg.content));
|
||||||
|
}
|
||||||
|
parts.push("</conversation_history>".to_string());
|
||||||
|
parts.push(String::new());
|
||||||
|
parts.push(latest_user_content.to_string());
|
||||||
|
parts.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn chat<F, U, T, A>(
|
pub async fn chat<F, U, T, A>(
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
@@ -224,13 +262,25 @@ where
|
|||||||
if is_claude_code {
|
if is_claude_code {
|
||||||
use crate::llm::providers::claude_code::ClaudeCodeProvider;
|
use crate::llm::providers::claude_code::ClaudeCodeProvider;
|
||||||
|
|
||||||
let user_message = messages
|
let latest_user_content = messages
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|m| m.role == Role::User)
|
.find(|m| m.role == Role::User)
|
||||||
.map(|m| m.content.clone())
|
.map(|m| m.content.clone())
|
||||||
.ok_or_else(|| "No user message found".to_string())?;
|
.ok_or_else(|| "No user message found".to_string())?;
|
||||||
|
|
||||||
|
// When resuming with a session_id, Claude Code loads its own transcript
|
||||||
|
// from disk — the latest user message is sufficient. Without a
|
||||||
|
// session_id (e.g. after a page refresh) the prior conversation context
|
||||||
|
// would be lost because Claude Code only receives a single prompt
|
||||||
|
// string. In that case, prepend the conversation history so the LLM
|
||||||
|
// retains full context even though the session cannot be resumed.
|
||||||
|
let user_message = if config.session_id.is_some() {
|
||||||
|
latest_user_content
|
||||||
|
} else {
|
||||||
|
build_claude_code_context_prompt(&messages, &latest_user_content)
|
||||||
|
};
|
||||||
|
|
||||||
let project_root = state
|
let project_root = state
|
||||||
.get_project_root()
|
.get_project_root()
|
||||||
.unwrap_or_else(|_| std::path::PathBuf::from("."));
|
.unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||||
@@ -404,7 +454,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(ChatResult {
|
Ok(ChatResult {
|
||||||
messages: new_messages,
|
messages: current_history[2..].to_vec(),
|
||||||
session_id: None,
|
session_id: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1095,4 +1145,102 @@ mod tests {
|
|||||||
let result = execute_tool(&call, &state).await;
|
let result = execute_tool(&call, &state).await;
|
||||||
assert!(result.starts_with("Error:"), "unexpected result: {result}");
|
assert!(result.starts_with("Error:"), "unexpected result: {result}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// build_claude_code_context_prompt (Bug 245)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_prompt_single_message_returns_content_as_is() {
|
||||||
|
let messages = vec![Message {
|
||||||
|
role: Role::User,
|
||||||
|
content: "hello".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
}];
|
||||||
|
let result = build_claude_code_context_prompt(&messages, "hello");
|
||||||
|
assert_eq!(result, "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_prompt_includes_prior_conversation() {
|
||||||
|
let messages = vec![
|
||||||
|
Message {
|
||||||
|
role: Role::User,
|
||||||
|
content: "What is Rust?".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
Message {
|
||||||
|
role: Role::Assistant,
|
||||||
|
content: "Rust is a systems language.".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
Message {
|
||||||
|
role: Role::User,
|
||||||
|
content: "Tell me more".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let result = build_claude_code_context_prompt(&messages, "Tell me more");
|
||||||
|
assert!(
|
||||||
|
result.contains("<conversation_history>"),
|
||||||
|
"should have history preamble"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.contains("[User]: What is Rust?"),
|
||||||
|
"should include prior user message"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.contains("[Assistant]: Rust is a systems language."),
|
||||||
|
"should include prior assistant message"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.contains("</conversation_history>"),
|
||||||
|
"should close history block"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.ends_with("Tell me more"),
|
||||||
|
"should end with latest user message"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_prompt_skips_system_messages() {
|
||||||
|
let messages = vec![
|
||||||
|
Message {
|
||||||
|
role: Role::System,
|
||||||
|
content: "You are a helpful assistant.".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
Message {
|
||||||
|
role: Role::User,
|
||||||
|
content: "hi".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
Message {
|
||||||
|
role: Role::Assistant,
|
||||||
|
content: "hello".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
Message {
|
||||||
|
role: Role::User,
|
||||||
|
content: "bye".to_string(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let result = build_claude_code_context_prompt(&messages, "bye");
|
||||||
|
assert!(
|
||||||
|
!result.contains("helpful assistant"),
|
||||||
|
"should not include system messages"
|
||||||
|
);
|
||||||
|
assert!(result.contains("[User]: hi"));
|
||||||
|
assert!(result.contains("[Assistant]: hello"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,8 +136,39 @@ pub async fn run_bot(
|
|||||||
slog!("[matrix-bot] Logged in as {bot_user_id} (device: {})", login_response.device_id);
|
slog!("[matrix-bot] Logged in as {bot_user_id} (device: {})", login_response.device_id);
|
||||||
|
|
||||||
// Bootstrap cross-signing keys for E2EE verification support.
|
// Bootstrap cross-signing keys for E2EE verification support.
|
||||||
if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
|
// Pass the bot's password for UIA (User-Interactive Authentication) —
|
||||||
slog!("[matrix-bot] Cross-signing bootstrap note: {e}");
|
// the homeserver requires proof of identity before accepting cross-signing keys.
|
||||||
|
{
|
||||||
|
use matrix_sdk::ruma::api::client::uiaa;
|
||||||
|
let password_auth = uiaa::AuthData::Password(uiaa::Password::new(
|
||||||
|
uiaa::UserIdentifier::UserIdOrLocalpart(config.username.clone()),
|
||||||
|
config.password.clone(),
|
||||||
|
));
|
||||||
|
if let Err(e) = client
|
||||||
|
.encryption()
|
||||||
|
.bootstrap_cross_signing(Some(password_auth))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
slog!("[matrix-bot] Cross-signing bootstrap note: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-sign own device keys so other clients don't show
|
||||||
|
// "encrypted by a device not verified by its owner" warnings.
|
||||||
|
match client.encryption().get_own_device().await {
|
||||||
|
Ok(Some(own_device)) => {
|
||||||
|
if own_device.is_cross_signed_by_owner() {
|
||||||
|
slog!("[matrix-bot] Device already self-signed");
|
||||||
|
} else {
|
||||||
|
slog!("[matrix-bot] Device not self-signed, signing now...");
|
||||||
|
match own_device.verify().await {
|
||||||
|
Ok(()) => slog!("[matrix-bot] Successfully self-signed device keys"),
|
||||||
|
Err(e) => slog!("[matrix-bot] Failed to self-sign device keys: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => slog!("[matrix-bot] Could not find own device in crypto store"),
|
||||||
|
Err(e) => slog!("[matrix-bot] Error retrieving own device: {e}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.allowed_users.is_empty() {
|
if config.allowed_users.is_empty() {
|
||||||
@@ -1234,6 +1265,32 @@ mod tests {
|
|||||||
assert_eq!(entries_b[0].content, "Room B message");
|
assert_eq!(entries_b[0].content, "Room B message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- self-sign device key decision logic -----------------------------------
|
||||||
|
|
||||||
|
// The self-signing logic in run_bot cannot be unit-tested because it
|
||||||
|
// requires a live matrix_sdk::Client. The tests below verify the branch
|
||||||
|
// decision: sign only when the device is NOT already cross-signed.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_already_self_signed_skips_signing() {
|
||||||
|
// Simulates: get_own_device returns Some, is_cross_signed_by_owner → true
|
||||||
|
let is_cross_signed: bool = true;
|
||||||
|
assert!(
|
||||||
|
is_cross_signed,
|
||||||
|
"already self-signed device should skip signing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_not_self_signed_triggers_signing() {
|
||||||
|
// Simulates: get_own_device returns Some, is_cross_signed_by_owner → false
|
||||||
|
let is_cross_signed: bool = false;
|
||||||
|
assert!(
|
||||||
|
!is_cross_signed,
|
||||||
|
"device without self-signature should trigger signing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// -- check_sender_verified decision logic --------------------------------
|
// -- check_sender_verified decision logic --------------------------------
|
||||||
|
|
||||||
// check_sender_verified cannot be called in unit tests because it requires
|
// check_sender_verified cannot be called in unit tests because it requires
|
||||||
|
|||||||
@@ -81,6 +81,24 @@ pub fn format_stage_notification(
|
|||||||
(plain, html)
|
(plain, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format an error notification message for a story failure.
|
||||||
|
///
|
||||||
|
/// Returns `(plain_text, html)` suitable for `RoomMessageEventContent::text_html`.
|
||||||
|
pub fn format_error_notification(
|
||||||
|
item_id: &str,
|
||||||
|
story_name: Option<&str>,
|
||||||
|
reason: &str,
|
||||||
|
) -> (String, String) {
|
||||||
|
let number = extract_story_number(item_id).unwrap_or(item_id);
|
||||||
|
let name = story_name.unwrap_or(item_id);
|
||||||
|
|
||||||
|
let plain = format!("\u{274c} #{number} {name} \u{2014} {reason}");
|
||||||
|
let html = format!(
|
||||||
|
"\u{274c} <strong>#{number}</strong> <em>{name}</em> \u{2014} {reason}"
|
||||||
|
);
|
||||||
|
(plain, html)
|
||||||
|
}
|
||||||
|
|
||||||
/// Spawn a background task that listens for watcher events and posts
|
/// Spawn a background task that listens for watcher events and posts
|
||||||
/// stage-transition notifications to all configured Matrix rooms.
|
/// stage-transition notifications to all configured Matrix rooms.
|
||||||
pub fn spawn_notification_listener(
|
pub fn spawn_notification_listener(
|
||||||
@@ -126,6 +144,32 @@ pub fn spawn_notification_listener(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(WatcherEvent::MergeFailure {
|
||||||
|
ref story_id,
|
||||||
|
ref reason,
|
||||||
|
}) => {
|
||||||
|
let story_name =
|
||||||
|
read_story_name(&project_root, "4_merge", story_id);
|
||||||
|
let (plain, html) = format_error_notification(
|
||||||
|
story_id,
|
||||||
|
story_name.as_deref(),
|
||||||
|
reason,
|
||||||
|
);
|
||||||
|
|
||||||
|
slog!("[matrix-bot] Sending error notification: {plain}");
|
||||||
|
|
||||||
|
for room_id in &room_ids {
|
||||||
|
if let Some(room) = client.get_room(room_id) {
|
||||||
|
let content =
|
||||||
|
RoomMessageEventContent::text_html(plain.clone(), html.clone());
|
||||||
|
if let Err(e) = room.send(content).await {
|
||||||
|
slog!(
|
||||||
|
"[matrix-bot] Failed to send error notification to {room_id}: {e}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(_) => {} // Ignore non-work-item events
|
Ok(_) => {} // Ignore non-work-item events
|
||||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||||
slog!(
|
slog!(
|
||||||
@@ -246,6 +290,42 @@ mod tests {
|
|||||||
assert_eq!(name, None);
|
assert_eq!(name, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── format_error_notification ────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_error_notification_with_story_name() {
|
||||||
|
let (plain, html) =
|
||||||
|
format_error_notification("262_story_bot_errors", Some("Bot error notifications"), "merge conflict in src/main.rs");
|
||||||
|
assert_eq!(
|
||||||
|
plain,
|
||||||
|
"\u{274c} #262 Bot error notifications \u{2014} merge conflict in src/main.rs"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"\u{274c} <strong>#262</strong> <em>Bot error notifications</em> \u{2014} merge conflict in src/main.rs"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_error_notification_without_story_name_falls_back_to_item_id() {
|
||||||
|
let (plain, _html) =
|
||||||
|
format_error_notification("42_bug_fix_thing", None, "tests failed");
|
||||||
|
assert_eq!(
|
||||||
|
plain,
|
||||||
|
"\u{274c} #42 42_bug_fix_thing \u{2014} tests failed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_error_notification_non_numeric_id_uses_full_id() {
|
||||||
|
let (plain, _html) =
|
||||||
|
format_error_notification("abc_story_thing", Some("Some Story"), "clippy errors");
|
||||||
|
assert_eq!(
|
||||||
|
plain,
|
||||||
|
"\u{274c} #abc_story_thing Some Story \u{2014} clippy errors"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── format_stage_notification ───────────────────────────────────────────
|
// ── format_stage_notification ───────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
1
vendor/rusqlite/.cargo-ok
vendored
1
vendor/rusqlite/.cargo-ok
vendored
@@ -1 +0,0 @@
|
|||||||
{"v":1}
|
|
||||||
6
vendor/rusqlite/.cargo_vcs_info.json
vendored
6
vendor/rusqlite/.cargo_vcs_info.json
vendored
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"git": {
|
|
||||||
"sha1": "2a1790a69107cd03dae85d501dcbdb11c5b32ef3"
|
|
||||||
},
|
|
||||||
"path_in_vcs": ""
|
|
||||||
}
|
|
||||||
3
vendor/rusqlite/.gitignore
vendored
3
vendor/rusqlite/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
/target/
|
|
||||||
/doc/
|
|
||||||
Cargo.lock
|
|
||||||
350
vendor/rusqlite/Cargo.toml
vendored
350
vendor/rusqlite/Cargo.toml
vendored
@@ -1,350 +0,0 @@
|
|||||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
|
||||||
#
|
|
||||||
# When uploading crates to the registry Cargo will automatically
|
|
||||||
# "normalize" Cargo.toml files for maximal compatibility
|
|
||||||
# with all versions of Cargo and also rewrite `path` dependencies
|
|
||||||
# to registry (e.g., crates.io) dependencies.
|
|
||||||
#
|
|
||||||
# If you are reading this file be aware that the original Cargo.toml
|
|
||||||
# will likely look very different (and much more reasonable).
|
|
||||||
# See Cargo.toml.orig for the original contents.
|
|
||||||
|
|
||||||
[package]
|
|
||||||
edition = "2021"
|
|
||||||
name = "rusqlite"
|
|
||||||
version = "0.37.99"
|
|
||||||
authors = ["The rusqlite developers"]
|
|
||||||
build = false
|
|
||||||
exclude = [
|
|
||||||
"/.github/*",
|
|
||||||
"/.gitattributes",
|
|
||||||
"/appveyor.yml",
|
|
||||||
"/Changelog.md",
|
|
||||||
"/clippy.toml",
|
|
||||||
"/codecov.yml",
|
|
||||||
"**/*.sh",
|
|
||||||
]
|
|
||||||
autolib = false
|
|
||||||
autobins = false
|
|
||||||
autoexamples = false
|
|
||||||
autotests = false
|
|
||||||
autobenches = false
|
|
||||||
description = "Ergonomic wrapper for SQLite"
|
|
||||||
documentation = "https://docs.rs/rusqlite/"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = [
|
|
||||||
"sqlite",
|
|
||||||
"database",
|
|
||||||
"ffi",
|
|
||||||
]
|
|
||||||
categories = ["database"]
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://github.com/rusqlite/rusqlite"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = [
|
|
||||||
"modern-full",
|
|
||||||
"rusqlite-macros",
|
|
||||||
]
|
|
||||||
all-features = false
|
|
||||||
no-default-features = false
|
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
|
||||||
rustdoc-args = [
|
|
||||||
"--cfg",
|
|
||||||
"docsrs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.playground]
|
|
||||||
features = ["bundled-full"]
|
|
||||||
all-features = false
|
|
||||||
|
|
||||||
[badges.appveyor]
|
|
||||||
repository = "rusqlite/rusqlite"
|
|
||||||
|
|
||||||
[badges.codecov]
|
|
||||||
repository = "rusqlite/rusqlite"
|
|
||||||
|
|
||||||
[badges.maintenance]
|
|
||||||
status = "actively-developed"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
array = [
|
|
||||||
"vtab",
|
|
||||||
"pointer",
|
|
||||||
]
|
|
||||||
backup = []
|
|
||||||
blob = []
|
|
||||||
buildtime_bindgen = [
|
|
||||||
"libsqlite3-sys/buildtime_bindgen",
|
|
||||||
"sqlite-wasm-rs/bindgen",
|
|
||||||
]
|
|
||||||
bundled = [
|
|
||||||
"libsqlite3-sys/bundled",
|
|
||||||
"modern_sqlite",
|
|
||||||
]
|
|
||||||
bundled-full = [
|
|
||||||
"modern-full",
|
|
||||||
"bundled",
|
|
||||||
]
|
|
||||||
bundled-sqlcipher = [
|
|
||||||
"libsqlite3-sys/bundled-sqlcipher",
|
|
||||||
"bundled",
|
|
||||||
]
|
|
||||||
bundled-sqlcipher-vendored-openssl = [
|
|
||||||
"libsqlite3-sys/bundled-sqlcipher-vendored-openssl",
|
|
||||||
"bundled-sqlcipher",
|
|
||||||
]
|
|
||||||
bundled-windows = ["libsqlite3-sys/bundled-windows"]
|
|
||||||
cache = ["hashlink"]
|
|
||||||
collation = []
|
|
||||||
column_decltype = []
|
|
||||||
column_metadata = ["libsqlite3-sys/column_metadata"]
|
|
||||||
csvtab = [
|
|
||||||
"csv",
|
|
||||||
"vtab",
|
|
||||||
]
|
|
||||||
default = ["cache"]
|
|
||||||
extra_check = []
|
|
||||||
fallible_uint = []
|
|
||||||
functions = []
|
|
||||||
hooks = []
|
|
||||||
i128_blob = []
|
|
||||||
in_gecko = [
|
|
||||||
"modern_sqlite",
|
|
||||||
"libsqlite3-sys/in_gecko",
|
|
||||||
]
|
|
||||||
limits = []
|
|
||||||
load_extension = []
|
|
||||||
loadable_extension = ["libsqlite3-sys/loadable_extension"]
|
|
||||||
modern-full = [
|
|
||||||
"array",
|
|
||||||
"backup",
|
|
||||||
"blob",
|
|
||||||
"modern_sqlite",
|
|
||||||
"chrono",
|
|
||||||
"collation",
|
|
||||||
"column_metadata",
|
|
||||||
"column_decltype",
|
|
||||||
"csvtab",
|
|
||||||
"extra_check",
|
|
||||||
"functions",
|
|
||||||
"hooks",
|
|
||||||
"i128_blob",
|
|
||||||
"jiff",
|
|
||||||
"limits",
|
|
||||||
"load_extension",
|
|
||||||
"serde_json",
|
|
||||||
"serialize",
|
|
||||||
"series",
|
|
||||||
"time",
|
|
||||||
"trace",
|
|
||||||
"unlock_notify",
|
|
||||||
"url",
|
|
||||||
"uuid",
|
|
||||||
"vtab",
|
|
||||||
"window",
|
|
||||||
]
|
|
||||||
modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
|
|
||||||
pointer = []
|
|
||||||
preupdate_hook = [
|
|
||||||
"libsqlite3-sys/preupdate_hook",
|
|
||||||
"hooks",
|
|
||||||
]
|
|
||||||
serialize = []
|
|
||||||
series = ["vtab"]
|
|
||||||
session = [
|
|
||||||
"libsqlite3-sys/session",
|
|
||||||
"hooks",
|
|
||||||
]
|
|
||||||
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
|
||||||
trace = []
|
|
||||||
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
|
||||||
vtab = []
|
|
||||||
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
|
|
||||||
window = ["functions"]
|
|
||||||
with-asan = ["libsqlite3-sys/with-asan"]
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "rusqlite"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "load_extension"
|
|
||||||
path = "examples/load_extension.rs"
|
|
||||||
required-features = [
|
|
||||||
"load_extension",
|
|
||||||
"bundled",
|
|
||||||
"functions",
|
|
||||||
"trace",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "loadable_extension"
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
path = "examples/loadable_extension.rs"
|
|
||||||
required-features = [
|
|
||||||
"loadable_extension",
|
|
||||||
"functions",
|
|
||||||
"trace",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "owning_rows"
|
|
||||||
path = "examples/owning_rows.rs"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "owning_statement"
|
|
||||||
path = "examples/owning_statement.rs"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "persons"
|
|
||||||
path = "examples/persons/main.rs"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "auto_ext"
|
|
||||||
path = "tests/auto_ext.rs"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "config_log"
|
|
||||||
path = "tests/config_log.rs"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "deny_single_threaded_sqlite_config"
|
|
||||||
path = "tests/deny_single_threaded_sqlite_config.rs"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "vtab"
|
|
||||||
path = "tests/vtab.rs"
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "cache"
|
|
||||||
path = "benches/cache.rs"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "exec"
|
|
||||||
path = "benches/exec.rs"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[dependencies.bitflags]
|
|
||||||
version = "2.6.0"
|
|
||||||
|
|
||||||
[dependencies.chrono]
|
|
||||||
version = "0.4.42"
|
|
||||||
features = ["clock"]
|
|
||||||
optional = true
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.csv]
|
|
||||||
version = "1.1"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.fallible-iterator]
|
|
||||||
version = "0.3"
|
|
||||||
|
|
||||||
[dependencies.fallible-streaming-iterator]
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
[dependencies.hashlink]
|
|
||||||
version = "0.11"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.jiff]
|
|
||||||
version = "0.2"
|
|
||||||
features = ["std"]
|
|
||||||
optional = true
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.rusqlite-macros]
|
|
||||||
version = "0.4.2"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.serde_json]
|
|
||||||
version = "1.0"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.smallvec]
|
|
||||||
version = "1.6.1"
|
|
||||||
|
|
||||||
[dependencies.time]
|
|
||||||
version = "0.3.47"
|
|
||||||
features = [
|
|
||||||
"formatting",
|
|
||||||
"macros",
|
|
||||||
"parsing",
|
|
||||||
]
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.url]
|
|
||||||
version = "2.1"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.uuid]
|
|
||||||
version = "1.0"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dev-dependencies.bencher]
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
[dev-dependencies.doc-comment]
|
|
||||||
version = "0.3"
|
|
||||||
|
|
||||||
[dev-dependencies.regex]
|
|
||||||
version = "1.5.5"
|
|
||||||
|
|
||||||
[dev-dependencies.self_cell]
|
|
||||||
version = "1.1.0"
|
|
||||||
|
|
||||||
[dev-dependencies.tempfile]
|
|
||||||
version = "3.1.0"
|
|
||||||
|
|
||||||
[dev-dependencies.unicase]
|
|
||||||
version = "2.6.0"
|
|
||||||
|
|
||||||
[dev-dependencies.uuid]
|
|
||||||
version = "1.0"
|
|
||||||
features = ["v4"]
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.chrono]
|
|
||||||
version = "0.4.42"
|
|
||||||
features = ["wasmbind"]
|
|
||||||
optional = true
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.jiff]
|
|
||||||
version = "0.2"
|
|
||||||
features = ["js"]
|
|
||||||
optional = true
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.sqlite-wasm-rs]
|
|
||||||
version = "0.5.1"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.time]
|
|
||||||
version = "0.3.47"
|
|
||||||
features = ["wasm-bindgen"]
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies.uuid]
|
|
||||||
version = "1.0"
|
|
||||||
features = ["js"]
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.getrandom]
|
|
||||||
version = "0.4"
|
|
||||||
features = ["wasm_js"]
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.uuid]
|
|
||||||
version = "1.0"
|
|
||||||
features = ["js"]
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.wasm-bindgen]
|
|
||||||
version = "0.2.104"
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies.wasm-bindgen-test]
|
|
||||||
version = "0.3.54"
|
|
||||||
|
|
||||||
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies.libsqlite3-sys]
|
|
||||||
version = "0.37.0"
|
|
||||||
242
vendor/rusqlite/Cargo.toml.orig
generated
vendored
242
vendor/rusqlite/Cargo.toml.orig
generated
vendored
@@ -1,242 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "rusqlite"
|
|
||||||
# Note: Update version in README.md when you change this.
|
|
||||||
version = "0.37.99"
|
|
||||||
authors = ["The rusqlite developers"]
|
|
||||||
edition = "2021"
|
|
||||||
description = "Ergonomic wrapper for SQLite"
|
|
||||||
repository = "https://github.com/rusqlite/rusqlite"
|
|
||||||
documentation = "https://docs.rs/rusqlite/"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["sqlite", "database", "ffi"]
|
|
||||||
license = "MIT"
|
|
||||||
categories = ["database"]
|
|
||||||
|
|
||||||
exclude = [
|
|
||||||
"/.github/*",
|
|
||||||
"/.gitattributes",
|
|
||||||
"/appveyor.yml",
|
|
||||||
"/Changelog.md",
|
|
||||||
"/clippy.toml",
|
|
||||||
"/codecov.yml",
|
|
||||||
"**/*.sh",
|
|
||||||
]
|
|
||||||
|
|
||||||
[badges]
|
|
||||||
appveyor = { repository = "rusqlite/rusqlite" }
|
|
||||||
codecov = { repository = "rusqlite/rusqlite" }
|
|
||||||
maintenance = { status = "actively-developed" }
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "rusqlite"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = ["libsqlite3-sys"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# if not SQLITE_OMIT_LOAD_EXTENSION
|
|
||||||
load_extension = []
|
|
||||||
# hot-backup interface
|
|
||||||
backup = []
|
|
||||||
# if not SQLITE_OMIT_INCRBLOB
|
|
||||||
# sqlite3_blob
|
|
||||||
blob = []
|
|
||||||
# Prepared statements cache by connection (like https://www.sqlite.org/tclsqlite.html#cache)
|
|
||||||
cache = ["hashlink"]
|
|
||||||
# sqlite3_create_collation_v2
|
|
||||||
collation = []
|
|
||||||
# sqlite3_create_function_v2
|
|
||||||
functions = []
|
|
||||||
# sqlite3_log / sqlite3_trace_v2
|
|
||||||
trace = []
|
|
||||||
# Use bundled SQLite sources (instead of the one provided by your OS / distribution)
|
|
||||||
bundled = ["libsqlite3-sys/bundled", "modern_sqlite"]
|
|
||||||
# Use SQLCipher instead of SQLite
|
|
||||||
bundled-sqlcipher = ["libsqlite3-sys/bundled-sqlcipher", "bundled"]
|
|
||||||
bundled-sqlcipher-vendored-openssl = [
|
|
||||||
"libsqlite3-sys/bundled-sqlcipher-vendored-openssl",
|
|
||||||
"bundled-sqlcipher",
|
|
||||||
]
|
|
||||||
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen", "sqlite-wasm-rs/bindgen"]
|
|
||||||
# sqlite3_limit
|
|
||||||
limits = []
|
|
||||||
# Used to generate a cdylib
|
|
||||||
loadable_extension = ["libsqlite3-sys/loadable_extension"]
|
|
||||||
# sqlite3_commit_hook, sqlite3_rollback_hook, ...
|
|
||||||
hooks = []
|
|
||||||
# if SQLITE_ENABLE_PREUPDATE_HOOK
|
|
||||||
preupdate_hook = ["libsqlite3-sys/preupdate_hook", "hooks"]
|
|
||||||
# u64, usize, NonZeroU64, NonZeroUsize
|
|
||||||
fallible_uint = []
|
|
||||||
i128_blob = []
|
|
||||||
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
|
||||||
# SQLITE_ENABLE_UNLOCK_NOTIFY
|
|
||||||
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
|
||||||
# if not SQLITE_OMIT_VIRTUALTABLE
|
|
||||||
# sqlite3_vtab
|
|
||||||
vtab = []
|
|
||||||
csvtab = ["csv", "vtab"]
|
|
||||||
# Port of Carray() table-valued function
|
|
||||||
array = ["vtab", "pointer"]
|
|
||||||
# if SQLITE_ENABLE_SESSION
|
|
||||||
# session extension
|
|
||||||
session = ["libsqlite3-sys/session", "hooks"]
|
|
||||||
# if not SQLITE_OMIT_WINDOWFUNC
|
|
||||||
# sqlite3_create_window_function
|
|
||||||
window = ["functions"]
|
|
||||||
# Port of generate_series table-valued function
|
|
||||||
series = ["vtab"]
|
|
||||||
# check for invalid query.
|
|
||||||
extra_check = []
|
|
||||||
# ]3.34.1, last]
|
|
||||||
modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
|
|
||||||
in_gecko = ["modern_sqlite", "libsqlite3-sys/in_gecko"]
|
|
||||||
bundled-windows = ["libsqlite3-sys/bundled-windows"]
|
|
||||||
# Build bundled sqlite with -fsanitize=address
|
|
||||||
with-asan = ["libsqlite3-sys/with-asan"]
|
|
||||||
# if SQLITE_ENABLE_COLUMN_METADATA
|
|
||||||
column_metadata = ["libsqlite3-sys/column_metadata"]
|
|
||||||
# if not SQLITE_OMIT_DECLTYPE
|
|
||||||
column_decltype = []
|
|
||||||
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
|
|
||||||
# if not SQLITE_OMIT_DESERIALIZE
|
|
||||||
serialize = []
|
|
||||||
# pointer passing interfaces: 3.20.0
|
|
||||||
pointer = []
|
|
||||||
|
|
||||||
# Helper feature for enabling most non-build-related optional features
|
|
||||||
# or dependencies (except `session`). This is useful for running tests / clippy
|
|
||||||
# / etc. New features and optional dependencies that don't conflict with anything
|
|
||||||
# else should be added here.
|
|
||||||
modern-full = [
|
|
||||||
"array",
|
|
||||||
"backup",
|
|
||||||
"blob",
|
|
||||||
"modern_sqlite",
|
|
||||||
"chrono",
|
|
||||||
"collation",
|
|
||||||
"column_metadata",
|
|
||||||
"column_decltype",
|
|
||||||
"csvtab",
|
|
||||||
"extra_check",
|
|
||||||
"functions",
|
|
||||||
"hooks",
|
|
||||||
"i128_blob",
|
|
||||||
"jiff",
|
|
||||||
"limits",
|
|
||||||
"load_extension",
|
|
||||||
"serde_json",
|
|
||||||
"serialize",
|
|
||||||
"series",
|
|
||||||
"time",
|
|
||||||
"trace",
|
|
||||||
"unlock_notify",
|
|
||||||
"url",
|
|
||||||
"uuid",
|
|
||||||
"vtab",
|
|
||||||
"window",
|
|
||||||
]
|
|
||||||
|
|
||||||
bundled-full = ["modern-full", "bundled"]
|
|
||||||
default = ["cache"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# Jiff Date/Time/Timestamp persistence
|
|
||||||
jiff = { version = "0.2", optional = true, default-features = false, features = [
|
|
||||||
"std",
|
|
||||||
] }
|
|
||||||
# Date/Time/Timestamp persistence
|
|
||||||
time = { version = "0.3.47", features = [
|
|
||||||
"formatting",
|
|
||||||
"macros",
|
|
||||||
"parsing",
|
|
||||||
], optional = true }
|
|
||||||
bitflags = "2.6.0"
|
|
||||||
# LRU cache of statement
|
|
||||||
hashlink = { version = "0.11", optional = true }
|
|
||||||
# Chrono Date/Time/Timestamp persistence
|
|
||||||
chrono = { version = "0.4.42", optional = true, default-features = false, features = [
|
|
||||||
"clock",
|
|
||||||
] }
|
|
||||||
# JSON persistence
|
|
||||||
serde_json = { version = "1.0", optional = true }
|
|
||||||
# Virtual table
|
|
||||||
csv = { version = "1.1", optional = true }
|
|
||||||
# Url persistence
|
|
||||||
url = { version = "2.1", optional = true }
|
|
||||||
fallible-iterator = "0.3"
|
|
||||||
fallible-streaming-iterator = "0.1"
|
|
||||||
# Uuid persistence
|
|
||||||
uuid = { version = "1.0", optional = true }
|
|
||||||
smallvec = "1.6.1"
|
|
||||||
# WIP comptime checks
|
|
||||||
rusqlite-macros = { path = "rusqlite-macros", version = "0.4.2", optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies]
|
|
||||||
libsqlite3-sys = { path = "libsqlite3-sys", version = "0.37.0" }
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
|
|
||||||
sqlite-wasm-rs = { version = "0.5.1", default-features = false }
|
|
||||||
chrono = { version = "0.4.42", optional = true, default-features = false, features = ["wasmbind"] }
|
|
||||||
jiff = { version = "0.2", optional = true, default-features = false, features = ["js"] }
|
|
||||||
time = { version = "0.3.47", optional = true, features = ["wasm-bindgen"] }
|
|
||||||
uuid = { version = "1.0", optional = true, features = ["js"] }
|
|
||||||
|
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies]
|
|
||||||
# Something is dependent on them, we use feature to override it.
|
|
||||||
uuid = { version = "1.0", features = ["js"] }
|
|
||||||
getrandom = { version = "0.4", features = ["wasm_js"] }
|
|
||||||
wasm-bindgen-test = "0.3.54"
|
|
||||||
wasm-bindgen = "0.2.104"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
doc-comment = "0.3"
|
|
||||||
tempfile = "3.1.0"
|
|
||||||
regex = "1.5.5"
|
|
||||||
uuid = { version = "1.0", features = ["v4"] }
|
|
||||||
unicase = "2.6.0"
|
|
||||||
self_cell = "1.1.0"
|
|
||||||
# Use `bencher` over criterion because it builds much faster,
|
|
||||||
# and we don't have many benchmarks
|
|
||||||
bencher = "0.1"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "auto_ext"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "config_log"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "deny_single_threaded_sqlite_config"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "vtab"
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "cache"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "exec"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "loadable_extension"
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
required-features = ["loadable_extension", "functions", "trace"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "load_extension"
|
|
||||||
required-features = ["load_extension", "bundled", "functions", "trace"]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = ["modern-full", "rusqlite-macros"]
|
|
||||||
all-features = false
|
|
||||||
no-default-features = false
|
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[package.metadata.playground]
|
|
||||||
features = ["bundled-full"]
|
|
||||||
all-features = false
|
|
||||||
19
vendor/rusqlite/LICENSE
vendored
19
vendor/rusqlite/LICENSE
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2014 The rusqlite developers
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
259
vendor/rusqlite/README.md
vendored
259
vendor/rusqlite/README.md
vendored
@@ -1,259 +0,0 @@
|
|||||||
# Rusqlite
|
|
||||||
|
|
||||||
[](https://crates.io/crates/rusqlite)
|
|
||||||
[](https://docs.rs/rusqlite)
|
|
||||||
[](https://github.com/rusqlite/rusqlite/actions)
|
|
||||||
[](https://ci.appveyor.com/project/rusqlite/rusqlite)
|
|
||||||
[](https://codecov.io/gh/rusqlite/rusqlite)
|
|
||||||
[](https://deps.rs/repo/github/rusqlite/rusqlite)
|
|
||||||
[](https://discord.gg/nFYfGPB8g4)
|
|
||||||
|
|
||||||
Rusqlite is an ergonomic wrapper for using SQLite from Rust.
|
|
||||||
|
|
||||||
Historically, the API was based on the one from [`rust-postgres`](https://github.com/sfackler/rust-postgres). However, the two have diverged in many ways, and no compatibility between the two is intended.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
In your Cargo.toml:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
# `bundled` causes us to automatically compile and link in an up to date
|
|
||||||
# version of SQLite for you. This avoids many common build issues, and
|
|
||||||
# avoids depending on the version of SQLite on the users system (or your
|
|
||||||
# system), which may be old or missing. It's the right choice for most
|
|
||||||
# programs that control their own SQLite databases.
|
|
||||||
#
|
|
||||||
# That said, it's not ideal for all scenarios and in particular, generic
|
|
||||||
# libraries built around `rusqlite` should probably not enable it, which
|
|
||||||
# is why it is not a default feature -- it could become hard to disable.
|
|
||||||
rusqlite = { version = "0.39.0", features = ["bundled"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
Simple example usage:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use rusqlite::{Connection, Result};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Person {
|
|
||||||
id: i32,
|
|
||||||
name: String,
|
|
||||||
data: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
conn.execute(
|
|
||||||
"CREATE TABLE person (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
data BLOB
|
|
||||||
)",
|
|
||||||
(), // empty list of parameters.
|
|
||||||
)?;
|
|
||||||
let me = Person {
|
|
||||||
id: 0,
|
|
||||||
name: "Steven".to_string(),
|
|
||||||
data: None,
|
|
||||||
};
|
|
||||||
conn.execute(
|
|
||||||
"INSERT INTO person (name, data) VALUES (?1, ?2)",
|
|
||||||
(&me.name, &me.data),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
|
|
||||||
let person_iter = stmt.query_map([], |row| {
|
|
||||||
Ok(Person {
|
|
||||||
id: row.get(0)?,
|
|
||||||
name: row.get(1)?,
|
|
||||||
data: row.get(2)?,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for person in person_iter {
|
|
||||||
println!("Found person {:?}", person.unwrap());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Supported SQLite Versions
|
|
||||||
|
|
||||||
The base `rusqlite` package supports SQLite version 3.34.1 or newer. If you need
|
|
||||||
support for older versions, please file an issue. Some cargo features require a
|
|
||||||
newer SQLite version; see details below.
|
|
||||||
|
|
||||||
### Optional Features
|
|
||||||
|
|
||||||
Rusqlite provides several features that are behind [Cargo
|
|
||||||
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
|
|
||||||
|
|
||||||
* [`load_extension`](https://docs.rs/rusqlite/~0/rusqlite/struct.LoadExtensionGuard.html)
|
|
||||||
allows loading dynamic library-based SQLite extensions.
|
|
||||||
* `loadable_extension` to program [loadable extension](https://sqlite.org/loadext.html) in Rust.
|
|
||||||
* [`backup`](https://docs.rs/rusqlite/~0/rusqlite/backup/index.html)
|
|
||||||
allows use of SQLite's online backup API.
|
|
||||||
* [`functions`](https://docs.rs/rusqlite/~0/rusqlite/functions/index.html)
|
|
||||||
allows you to load Rust closures into SQLite connections for use in queries.
|
|
||||||
* `window` for [window function](https://www.sqlite.org/windowfunctions.html) support (`fun(...) OVER ...`). (Implies `functions`.)
|
|
||||||
* [`trace`](https://docs.rs/rusqlite/~0/rusqlite/trace/index.html)
|
|
||||||
allows hooks into SQLite's tracing and profiling APIs.
|
|
||||||
* [`blob`](https://docs.rs/rusqlite/~0/rusqlite/blob/index.html)
|
|
||||||
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs.
|
|
||||||
* [`limits`](https://docs.rs/rusqlite/~0/rusqlite/struct.Connection.html#method.limit)
|
|
||||||
allows you to set and retrieve SQLite's per connection limits.
|
|
||||||
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
|
|
||||||
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
|
|
||||||
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various
|
|
||||||
types from the [`chrono` crate](https://crates.io/crates/chrono).
|
|
||||||
* `time` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various
|
|
||||||
types from the [`time` crate](https://crates.io/crates/time).
|
|
||||||
* `jiff` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
|
|
||||||
`Value` type from the [`jiff` crate](https://crates.io/crates/jiff).
|
|
||||||
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
|
|
||||||
`Url` type from the [`url` crate](https://crates.io/crates/url).
|
|
||||||
* `bundled` uses a bundled version of SQLite. This is a good option for cases where linking to SQLite is complicated, such as Windows.
|
|
||||||
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature overrides `bundled`.
|
|
||||||
* `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation.
|
|
||||||
* `bundled-sqlcipher-vendored-openssl` allows using bundled-sqlcipher with a vendored version of OpenSSL (via the `openssl-sys` crate) as the crypto provider.
|
|
||||||
- As the name implies this depends on the `bundled-sqlcipher` feature, and automatically turns it on.
|
|
||||||
- If turned on, this uses the [`openssl-sys`](https://crates.io/crates/openssl-sys) crate, with the `vendored` feature enabled in order to build and bundle the OpenSSL crypto library.
|
|
||||||
* `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks.
|
|
||||||
* `preupdate_hook` for [preupdate](https://sqlite.org/c3ref/preupdate_count.html) notification callbacks. (Implies `hooks`.)
|
|
||||||
* `unlock_notify` for [Unlock](https://sqlite.org/unlock_notify.html) notification.
|
|
||||||
* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implementations in Rust). Currently, only read-only virtual tables are supported.
|
|
||||||
* `series` exposes [`generate_series(...)`](https://www.sqlite.org/series.html) Table-Valued Function. (Implies `vtab`.)
|
|
||||||
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. (Implies `vtab`.)
|
|
||||||
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. (Implies `vtab`.)
|
|
||||||
* `fallible_uint` allows storing values of type `u64`, `usize`, `NonZeroU64`, `NonZeroUsize` but only if <= `i64::MAX`.
|
|
||||||
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
|
|
||||||
* `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) crate using blobs.
|
|
||||||
* [`session`](https://sqlite.org/sessionintro.html), Session module extension. Requires `buildtime_bindgen` feature. (Implies `hooks`.)
|
|
||||||
* `extra_check` fails when a query passed to `execute` is readonly and has a column count > 0.
|
|
||||||
* `column_decltype` provides `columns()` method for Statements and Rows; omit if linking to a version of SQLite/SQLCipher compiled with `-DSQLITE_OMIT_DECLTYPE`.
|
|
||||||
* `collation` exposes [`sqlite3_create_collation_v2`](https://sqlite.org/c3ref/create_collation.html).
|
|
||||||
* `serialize` exposes [`sqlite3_serialize`](http://sqlite.org/c3ref/serialize.html) (3.23.0).
|
|
||||||
* `rusqlite-macros` enables the use of the [`prepare_and_bind`](https://docs.rs/rusqlite/~0/rusqlite/macro.prepare_and_bind.html)
|
|
||||||
and [`prepare_cached_and_bind`](https://docs.rs/rusqlite/~0/rusqlite/macro.prepare_cached_and_bind.html)
|
|
||||||
procedural macros, which allow capturing identifiers in SQL statements.
|
|
||||||
|
|
||||||
|
|
||||||
## Notes on building rusqlite and libsqlite3-sys
|
|
||||||
|
|
||||||
`libsqlite3-sys` is a separate crate from `rusqlite` that provides the Rust
|
|
||||||
declarations for SQLite's C API. By default, `libsqlite3-sys` attempts to find a SQLite library that already exists on your system using pkg-config, or a
|
|
||||||
[Vcpkg](https://github.com/Microsoft/vcpkg) installation for MSVC ABI builds.
|
|
||||||
|
|
||||||
You can adjust this behavior in a number of ways:
|
|
||||||
|
|
||||||
* If you use the `bundled`, `bundled-sqlcipher`, or `bundled-sqlcipher-vendored-openssl` features, `libsqlite3-sys` will use the
|
|
||||||
[cc](https://crates.io/crates/cc) crate to compile SQLite or SQLCipher from source and
|
|
||||||
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
|
||||||
is currently SQLite 3.51.3 (as of `rusqlite` 0.39.0 / `libsqlite3-sys`
|
|
||||||
0.37.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
|
||||||
```toml
|
|
||||||
[dependencies.rusqlite]
|
|
||||||
version = "0.39.0"
|
|
||||||
features = ["bundled"]
|
|
||||||
```
|
|
||||||
* When using any of the `bundled` features, the build script will honor `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_EXPR_DEPTH` variables. It will also honor a `LIBSQLITE3_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.)
|
|
||||||
* When using `bundled-sqlcipher` (and not also using `bundled-sqlcipher-vendored-openssl`), `libsqlite3-sys` will need to
|
|
||||||
link against crypto libraries on the system. If the build script can find a `libcrypto` from OpenSSL or LibreSSL (it will consult `OPENSSL_LIB_DIR`/`OPENSSL_INCLUDE_DIR` and `OPENSSL_DIR` environment variables), it will use that. If building on and for Macs, and none of those variables are set, it will use the system's SecurityFramework instead.
|
|
||||||
|
|
||||||
* When linking against a SQLite (or SQLCipher) library already on the system (so *not* using any of the `bundled` features), you can set the `SQLITE3_LIB_DIR` (or `SQLCIPHER_LIB_DIR`) environment variable to point to a directory containing the library. You can also set the `SQLITE3_INCLUDE_DIR` (or `SQLCIPHER_INCLUDE_DIR`) variable to point to the directory containing `sqlite3.h`.
|
|
||||||
* Installing the sqlite3 development packages will usually be all that is required, but
|
|
||||||
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
|
|
||||||
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
|
|
||||||
options. The default when using vcpkg is to dynamically link,
|
|
||||||
which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
|
|
||||||
`vcpkg install sqlite3:x64-windows` will install the required library.
|
|
||||||
* When linking against a SQLite (or SQLCipher) library already on the system, you can set the `SQLITE3_STATIC` (or `SQLCIPHER_STATIC`) environment variable to 1 to request that the library be statically instead of dynamically linked.
|
|
||||||
|
|
||||||
|
|
||||||
### Binding generation
|
|
||||||
|
|
||||||
We use [bindgen](https://crates.io/crates/bindgen) to generate the Rust
|
|
||||||
declarations from SQLite's C header file. `bindgen`
|
|
||||||
[recommends](https://github.com/servo/rust-bindgen#library-usage-with-buildrs)
|
|
||||||
running this as part of the build process of libraries that used this. We tried
|
|
||||||
this briefly (`rusqlite` 0.10.0, specifically), but it had some annoyances:
|
|
||||||
|
|
||||||
* The build time for `libsqlite3-sys` (and therefore `rusqlite`) increased
|
|
||||||
dramatically.
|
|
||||||
* Running `bindgen` requires a relatively-recent version of Clang, which many
|
|
||||||
systems do not have installed by default.
|
|
||||||
* Running `bindgen` also requires the SQLite header file to be present.
|
|
||||||
|
|
||||||
As of `rusqlite` 0.10.1, we avoid running `bindgen` at build-time by shipping
|
|
||||||
pregenerated bindings for several versions of SQLite. When compiling
|
|
||||||
`rusqlite`, we use your selected Cargo features to pick the bindings for the
|
|
||||||
minimum SQLite version that supports your chosen features. If you are using
|
|
||||||
`libsqlite3-sys` directly, you can use the same features to choose which
|
|
||||||
pregenerated bindings are chosen:
|
|
||||||
|
|
||||||
* `min_sqlite_version_3_34_1` - SQLite 3.34.1 bindings (this is the default)
|
|
||||||
|
|
||||||
If you use any of the `bundled` features, you will get pregenerated bindings for the
|
|
||||||
bundled version of SQLite/SQLCipher. If you need other specific pregenerated binding
|
|
||||||
versions, please file an issue. If you want to run `bindgen` at buildtime to
|
|
||||||
produce your own bindings, use the `buildtime_bindgen` Cargo feature.
|
|
||||||
|
|
||||||
If you enable the `modern_sqlite` feature, we'll use the bindings we would have
|
|
||||||
included with the bundled build. You generally should have `buildtime_bindgen`
|
|
||||||
enabled if you turn this on, as otherwise you'll need to keep the version of
|
|
||||||
SQLite you link with in sync with what rusqlite would have bundled, (usually the
|
|
||||||
most recent release of SQLite). Failing to do this will cause a runtime error.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Rusqlite has many features, and many of them impact the build configuration in
|
|
||||||
incompatible ways. This is unfortunate, and makes testing changes hard.
|
|
||||||
|
|
||||||
To help here: you generally should ensure that you run tests/lint for
|
|
||||||
`--features bundled`, and `--features "bundled-full session buildtime_bindgen"`.
|
|
||||||
|
|
||||||
If running bindgen is problematic for you, `--features bundled-full` enables
|
|
||||||
bundled and all features which don't require binding generation, and can be used
|
|
||||||
instead.
|
|
||||||
|
|
||||||
### Checklist
|
|
||||||
|
|
||||||
- Run `cargo fmt` to ensure your Rust code is correctly formatted.
|
|
||||||
- Ensure `cargo clippy --workspace --features bundled` passes without warnings.
|
|
||||||
- Ensure `cargo clippy --workspace --features "bundled-full session buildtime_bindgen"` passes without warnings.
|
|
||||||
- Ensure `cargo test --workspace --features bundled` reports no failures.
|
|
||||||
- Ensure `cargo test --workspace --features "bundled-full session buildtime_bindgen"` reports no failures.
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
Rusqlite is the product of hard work by a number of people. A list is available
|
|
||||||
here: https://github.com/rusqlite/rusqlite/graphs/contributors
|
|
||||||
|
|
||||||
## Community
|
|
||||||
|
|
||||||
Feel free to join the [Rusqlite Discord Server](https://discord.gg/nFYfGPB8g4) to discuss or get help with `rusqlite` or `libsqlite3-sys`.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Rusqlite and libsqlite3-sys are available under the MIT license. See the LICENSE file for more info.
|
|
||||||
|
|
||||||
### Licenses of Bundled Software
|
|
||||||
|
|
||||||
Depending on the set of enabled cargo `features`, rusqlite and libsqlite3-sys will also bundle other libraries, which have their own licensing terms:
|
|
||||||
|
|
||||||
- If `--features=bundled-sqlcipher` is enabled, the vendored source of [SQLcipher](https://github.com/sqlcipher/sqlcipher) will be compiled and statically linked in. SQLcipher is distributed under a BSD-style license, as described [here](libsqlite3-sys/sqlcipher/LICENSE).
|
|
||||||
|
|
||||||
- If `--features=bundled` is enabled, the vendored source of SQLite will be compiled and linked in. SQLite is in the public domain, as described [here](https://www.sqlite.org/copyright.html).
|
|
||||||
|
|
||||||
Both of these are quite permissive, have no bearing on the license of the code in `rusqlite` or `libsqlite3-sys` themselves, and can be entirely ignored if you do not use the feature in question.
|
|
||||||
|
|
||||||
## Minimum supported Rust version (MSRV)
|
|
||||||
|
|
||||||
Latest stable Rust version at the time of release. It might compile with older versions.
|
|
||||||
18
vendor/rusqlite/benches/cache.rs
vendored
18
vendor/rusqlite/benches/cache.rs
vendored
@@ -1,18 +0,0 @@
|
|||||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
|
||||||
use rusqlite::Connection;
|
|
||||||
|
|
||||||
fn bench_no_cache(b: &mut Bencher) {
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
|
||||||
db.set_prepared_statement_cache_capacity(0);
|
|
||||||
let sql = "SELECT 1, 'test', 3.14 UNION SELECT 2, 'exp', 2.71";
|
|
||||||
b.iter(|| db.prepare(sql).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_cache(b: &mut Bencher) {
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
|
||||||
let sql = "SELECT 1, 'test', 3.14 UNION SELECT 2, 'exp', 2.71";
|
|
||||||
b.iter(|| db.prepare_cached(sql).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark_group!(cache_benches, bench_no_cache, bench_cache);
|
|
||||||
benchmark_main!(cache_benches);
|
|
||||||
17
vendor/rusqlite/benches/exec.rs
vendored
17
vendor/rusqlite/benches/exec.rs
vendored
@@ -1,17 +0,0 @@
|
|||||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
|
||||||
use rusqlite::Connection;
|
|
||||||
|
|
||||||
fn bench_execute(b: &mut Bencher) {
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
|
||||||
let sql = "PRAGMA user_version=1";
|
|
||||||
b.iter(|| db.execute(sql, []).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_execute_batch(b: &mut Bencher) {
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
|
||||||
let sql = "PRAGMA user_version=1";
|
|
||||||
b.iter(|| db.execute_batch(sql).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark_group!(exec_benches, bench_execute, bench_execute_batch);
|
|
||||||
benchmark_main!(exec_benches);
|
|
||||||
405
vendor/rusqlite/bindings.md
vendored
405
vendor/rusqlite/bindings.md
vendored
@@ -1,405 +0,0 @@
|
|||||||
# List of SQLite functions supported
|
|
||||||
|
|
||||||
- [ ] `sqlite3_version`
|
|
||||||
- [X] `sqlite3_libversion`
|
|
||||||
- [ ] `sqlite3_sourceid`
|
|
||||||
- [X] `sqlite3_libversion_number`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_compileoption_used`
|
|
||||||
- [ ] `sqlite3_compileoption_get`
|
|
||||||
|
|
||||||
- [X] `sqlite3_threadsafe` (internal use only)
|
|
||||||
|
|
||||||
- [X] `sqlite3_close`
|
|
||||||
- [ ] `sqlite3_close_v2`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_exec`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_initialize`
|
|
||||||
- [ ] `sqlite3_shutdown`
|
|
||||||
- [ ] `sqlite3_os_init`
|
|
||||||
- [ ] `sqlite3_os_end`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_config` (partially, `fn` callback for SQLITE_CONFIG_LOG) (cannot be used by a loadable extension)
|
|
||||||
- [X] `sqlite3_db_config`
|
|
||||||
|
|
||||||
- [X] `sqlite3_extended_result_codes` (not public, internal use only)
|
|
||||||
|
|
||||||
- [X] `sqlite3_last_insert_rowid`
|
|
||||||
- [ ] `sqlite3_set_last_insert_rowid`
|
|
||||||
|
|
||||||
- [X] `sqlite3_changes`
|
|
||||||
- [X] `sqlite3_changes64`
|
|
||||||
- [X] `sqlite3_total_changes`
|
|
||||||
- [X] `sqlite3_total_changes64`
|
|
||||||
|
|
||||||
- [X] `sqlite3_interrupt`
|
|
||||||
- [X] `sqlite3_is_interrupted`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_complete`
|
|
||||||
|
|
||||||
- [X] `sqlite3_busy_handler` (`fn` callback)
|
|
||||||
- [X] `sqlite3_busy_timeout`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_get_table`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_mprintf`
|
|
||||||
- [ ] `sqlite3_vmprintf`
|
|
||||||
- [ ] `sqlite3_snprintf`
|
|
||||||
- [ ] `sqlite3_vsnprintf`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_malloc`
|
|
||||||
- [X] `sqlite3_malloc64` (not public, internal use only)
|
|
||||||
- [ ] `sqlite3_realloc`
|
|
||||||
- [ ] `sqlite3_realloc64`
|
|
||||||
- [X] `sqlite3_free` (not public, internal use only)
|
|
||||||
- [ ] `sqlite3_msize`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_memory_used`
|
|
||||||
- [ ] `sqlite3_memory_highwater`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_randomness`
|
|
||||||
|
|
||||||
- [X] `sqlite3_set_authorizer` (`FnMut` callback, reference kept)
|
|
||||||
- [X] `sqlite3_trace` deprecated (`fn` callback)
|
|
||||||
- [X] `sqlite3_profile` deprecated (`fn` callback)
|
|
||||||
- [X] `sqlite3_trace_v2` (`fn` callback, no context data)
|
|
||||||
- [X] `sqlite3_progress_handler` (`FnMut` callback, reference kept)
|
|
||||||
|
|
||||||
- [ ] `sqlite3_open`
|
|
||||||
- [X] `sqlite3_open_v2`
|
|
||||||
- [ ] `sqlite3_uri_parameter`
|
|
||||||
- [ ] `sqlite3_uri_boolean`
|
|
||||||
- [ ] `sqlite3_uri_int64`
|
|
||||||
- [ ] `sqlite3_uri_key`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_filename_database`
|
|
||||||
- [ ] `sqlite3_filename_journal`
|
|
||||||
- [ ] `sqlite3_filename_wal`
|
|
||||||
- [ ] `sqlite3_database_file_object`
|
|
||||||
- [ ] `sqlite3_create_filename`
|
|
||||||
- [ ] `sqlite3_free_filename`
|
|
||||||
|
|
||||||
- [X] `sqlite3_errcode`
|
|
||||||
- [X] `sqlite3_extended_errcode`
|
|
||||||
- [X] `sqlite3_errmsg` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_errstr` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_error_offset`
|
|
||||||
|
|
||||||
- [X] `sqlite3_limit`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_prepare`
|
|
||||||
- [X] `sqlite3_prepare_v2`
|
|
||||||
- [X] `sqlite3_prepare_v3`
|
|
||||||
|
|
||||||
- [X] `sqlite3_sql` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_expanded_sql`
|
|
||||||
- [ ] `sqlite3_normalized_sql`
|
|
||||||
|
|
||||||
- [X] `sqlite3_stmt_readonly`
|
|
||||||
- [X] `sqlite3_stmt_isexplain`
|
|
||||||
- [ ] `sqlite3_stmt_explain`
|
|
||||||
- [X] `sqlite3_stmt_busy`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_bind_blob`
|
|
||||||
- [X] `sqlite3_bind_blob64`
|
|
||||||
- [X] `sqlite3_bind_double`
|
|
||||||
- [ ] `sqlite3_bind_int`
|
|
||||||
- [X] `sqlite3_bind_int64`
|
|
||||||
- [X] `sqlite3_bind_null`
|
|
||||||
- [ ] `sqlite3_bind_text`
|
|
||||||
- [X] `sqlite3_bind_text64`
|
|
||||||
- [ ] `sqlite3_bind_value`
|
|
||||||
- [X] `sqlite3_bind_pointer`
|
|
||||||
- [X] `sqlite3_bind_zeroblob`
|
|
||||||
- [ ] `sqlite3_bind_zeroblob64`
|
|
||||||
|
|
||||||
- [X] `sqlite3_bind_parameter_count`
|
|
||||||
- [X] `sqlite3_bind_parameter_name`
|
|
||||||
- [X] `sqlite3_bind_parameter_index`
|
|
||||||
- [X] `sqlite3_clear_bindings`
|
|
||||||
|
|
||||||
- [X] `sqlite3_column_count`
|
|
||||||
- [ ] `sqlite3_data_count`
|
|
||||||
- [X] `sqlite3_column_name`
|
|
||||||
- [X] `sqlite3_column_database_name`
|
|
||||||
- [X] `sqlite3_column_table_name`
|
|
||||||
- [X] `sqlite3_column_origin_name`
|
|
||||||
- [X] `sqlite3_column_decltype`
|
|
||||||
|
|
||||||
- [X] `sqlite3_step`
|
|
||||||
|
|
||||||
- [X] `sqlite3_column_blob`
|
|
||||||
- [X] `sqlite3_column_double`
|
|
||||||
- [ ] `sqlite3_column_int`
|
|
||||||
- [X] `sqlite3_column_int64`
|
|
||||||
- [X] `sqlite3_column_text`
|
|
||||||
- [X] `sqlite3_column_value` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_column_bytes` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_column_type`
|
|
||||||
|
|
||||||
- [X] `sqlite3_finalize`
|
|
||||||
- [X] `sqlite3_reset` (not public, internal use only)
|
|
||||||
|
|
||||||
- [ ] `sqlite3_create_function`
|
|
||||||
- [X] `sqlite3_create_function_v2` (Boxed callback, destroyed by SQLite)
|
|
||||||
- [X] `sqlite3_create_window_function` (Boxed callback, destroyed by SQLite)
|
|
||||||
|
|
||||||
- [X] `sqlite3_value_blob`
|
|
||||||
- [X] `sqlite3_value_double`
|
|
||||||
- [ ] `sqlite3_value_int`
|
|
||||||
- [X] `sqlite3_value_int64`
|
|
||||||
- [X] `sqlite3_value_pointer`
|
|
||||||
- [X] `sqlite3_value_text`
|
|
||||||
- [X] `sqlite3_value_bytes` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_value_type`
|
|
||||||
- [ ] `sqlite3_value_numeric_type`
|
|
||||||
- [X] `sqlite3_value_nochange`
|
|
||||||
- [ ] `sqlite3_value_frombind`
|
|
||||||
- [ ] `sqlite3_value_encoding`
|
|
||||||
- [X] `sqlite3_value_subtype`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_value_dup`
|
|
||||||
- [ ] `sqlite3_value_free`
|
|
||||||
|
|
||||||
- [X] `sqlite3_aggregate_context` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_user_data` (not public, internal use only)
|
|
||||||
- [X] `sqlite3_context_db_handle` (Connection ref)
|
|
||||||
- [X] `sqlite3_get_auxdata`
|
|
||||||
- [X] `sqlite3_set_auxdata`
|
|
||||||
- [ ] `sqlite3_get_clientdata`
|
|
||||||
- [ ] `sqlite3_set_clientdata`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_result_blob`
|
|
||||||
- [X] `sqlite3_result_blob64`
|
|
||||||
- [X] `sqlite3_result_double`
|
|
||||||
- [X] `sqlite3_result_error`
|
|
||||||
- [X] `sqlite3_result_error_toobig`
|
|
||||||
- [X] `sqlite3_result_error_nomem`
|
|
||||||
- [X] `sqlite3_result_error_code`
|
|
||||||
- [ ] `sqlite3_result_int`
|
|
||||||
- [X] `sqlite3_result_int64`
|
|
||||||
- [X] `sqlite3_result_null`
|
|
||||||
- [ ] `sqlite3_result_text`
|
|
||||||
- [X] `sqlite3_result_text64`
|
|
||||||
- [X] `sqlite3_result_value`
|
|
||||||
- [X] `sqlite3_result_pointer`
|
|
||||||
- [X] `sqlite3_result_zeroblob`
|
|
||||||
- [ ] `sqlite3_result_zeroblob64`
|
|
||||||
- [X] `sqlite3_result_subtype`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_create_collation`
|
|
||||||
- [X] `sqlite3_create_collation_v2` (Boxed callback, destroyed by SQLite)
|
|
||||||
- [X] `sqlite3_collation_needed` (`fn` callback)
|
|
||||||
|
|
||||||
- [ ] `sqlite3_sleep`
|
|
||||||
|
|
||||||
- [X] `sqlite3_get_autocommit`
|
|
||||||
|
|
||||||
- [X] `sqlite3_db_handle` (not public, internal use only, Connection ref)
|
|
||||||
- [X] `sqlite3_db_name`
|
|
||||||
- [X] `sqlite3_db_filename`
|
|
||||||
- [X] `sqlite3_db_readonly`
|
|
||||||
- [X] `sqlite3_txn_state`
|
|
||||||
- [X] `sqlite3_next_stmt` (not public, internal use only)
|
|
||||||
|
|
||||||
- [X] `sqlite3_commit_hook` (`FnMut` callback, reference kept)
|
|
||||||
- [X] `sqlite3_rollback_hook` (`FnMut` callback, reference kept)
|
|
||||||
- [ ] `sqlite3_autovacuum_pages`
|
|
||||||
- [X] `sqlite3_update_hook` (`FnMut` callback, reference kept)
|
|
||||||
|
|
||||||
- [ ] `sqlite3_enable_shared_cache`
|
|
||||||
- [ ] `sqlite3_release_memory`
|
|
||||||
- [X] `sqlite3_db_release_memory`
|
|
||||||
- [ ] `sqlite3_soft_heap_limit64`
|
|
||||||
- [ ] `sqlite3_hard_heap_limit64`
|
|
||||||
|
|
||||||
- [X] `sqlite3_table_column_metadata`
|
|
||||||
|
|
||||||
- [X] `sqlite3_load_extension`
|
|
||||||
- [X] `sqlite3_enable_load_extension` (cannot be used by a loadable extension)
|
|
||||||
- [X] `sqlite3_auto_extension` (`fn` callbak with Connection ref)
|
|
||||||
- [X] `sqlite3_cancel_auto_extension`
|
|
||||||
- [X] `sqlite3_reset_auto_extension`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_create_module`
|
|
||||||
- [X] `sqlite3_create_module_v2`
|
|
||||||
- [ ] `sqlite3_drop_modules`
|
|
||||||
- [X] `sqlite3_declare_vtab`
|
|
||||||
- [ ] `sqlite3_overload_function`
|
|
||||||
|
|
||||||
- [X] `sqlite3_blob_open`
|
|
||||||
- [X] `sqlite3_blob_reopen`
|
|
||||||
- [X] `sqlite3_blob_close`
|
|
||||||
- [X] `sqlite3_blob_bytes`
|
|
||||||
- [X] `sqlite3_blob_read`
|
|
||||||
- [X] `sqlite3_blob_write`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_vfs_find`
|
|
||||||
- [ ] `sqlite3_vfs_register`
|
|
||||||
- [ ] `sqlite3_vfs_unregister`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_mutex_alloc`
|
|
||||||
- [ ] `sqlite3_mutex_free`
|
|
||||||
- [ ] `sqlite3_mutex_enter`
|
|
||||||
- [ ] `sqlite3_mutex_try`
|
|
||||||
- [ ] `sqlite3_mutex_leave`
|
|
||||||
- [ ] `sqlite3_mutex_held`
|
|
||||||
- [ ] `sqlite3_mutex_notheld`
|
|
||||||
- [ ] `sqlite3_db_mutex`
|
|
||||||
|
|
||||||
- [X] `sqlite3_file_control` (not public, internal use only)
|
|
||||||
- [ ] `sqlite3_test_control`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_keyword_count`
|
|
||||||
- [ ] `sqlite3_keyword_name`
|
|
||||||
- [ ] `sqlite3_keyword_check`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_str_new`
|
|
||||||
- [ ] `sqlite3_str_finish`
|
|
||||||
- [ ] `sqlite3_str_append`
|
|
||||||
- [ ] `sqlite3_str_reset`
|
|
||||||
- [ ] `sqlite3_str_errcode`
|
|
||||||
- [ ] `sqlite3_str_length`
|
|
||||||
- [ ] `sqlite3_str_value`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_status`
|
|
||||||
- [ ] `sqlite3_status64`
|
|
||||||
- [ ] `sqlite3_db_status`
|
|
||||||
- [X] `sqlite3_stmt_status`
|
|
||||||
|
|
||||||
- [X] `sqlite3_backup_init`
|
|
||||||
- [X] `sqlite3_backup_step`
|
|
||||||
- [X] `sqlite3_backup_finish`
|
|
||||||
- [X] `sqlite3_backup_remaining`
|
|
||||||
- [X] `sqlite3_backup_pagecount`
|
|
||||||
|
|
||||||
- [X] `sqlite3_unlock_notify` (`fn` callback, internal use only)
|
|
||||||
|
|
||||||
- [ ] `sqlite3_stricmp`
|
|
||||||
- [ ] `sqlite3_strnicmp`
|
|
||||||
- [ ] `sqlite3_strglob`
|
|
||||||
- [ ] `sqlite3_strlike`
|
|
||||||
|
|
||||||
- [X] `sqlite3_log`
|
|
||||||
|
|
||||||
- [X] `sqlite3_wal_hook` (`fn` callback with Connection ref)
|
|
||||||
- [ ] `sqlite3_wal_autocheckpoint`
|
|
||||||
- [X] `sqlite3_wal_checkpoint`
|
|
||||||
- [X] `sqlite3_wal_checkpoint_v2`
|
|
||||||
|
|
||||||
- [X] `sqlite3_vtab_config`
|
|
||||||
- [X] `sqlite3_vtab_on_conflict`
|
|
||||||
- [X] `sqlite3_vtab_nochange`
|
|
||||||
- [X] `sqlite3_vtab_collation`
|
|
||||||
- [X] `sqlite3_vtab_distinct`
|
|
||||||
- [X] `sqlite3_vtab_in`
|
|
||||||
- [X] `sqlite3_vtab_in_first`
|
|
||||||
- [X] `sqlite3_vtab_in_next`
|
|
||||||
- [X] `sqlite3_vtab_rhs_value`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_stmt_scanstatus`
|
|
||||||
- [ ] `sqlite3_stmt_scanstatus_v2`
|
|
||||||
- [ ] `sqlite3_stmt_scanstatus_reset`
|
|
||||||
|
|
||||||
- [X] `sqlite3_db_cacheflush`
|
|
||||||
|
|
||||||
- [X] `sqlite3_preupdate_hook` (`FnMut` callback with Connection ref, reference kept) (cannot be used by a loadable extension)
|
|
||||||
- [X] `sqlite3_preupdate_old`
|
|
||||||
- [X] `sqlite3_preupdate_count`
|
|
||||||
- [X] `sqlite3_preupdate_depth`
|
|
||||||
- [X] `sqlite3_preupdate_new`
|
|
||||||
- [ ] `sqlite3_preupdate_blobwrite`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_system_errno`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_snapshot_get`
|
|
||||||
- [ ] `sqlite3_snapshot_open`
|
|
||||||
- [ ] `sqlite3_snapshot_free`
|
|
||||||
- [ ] `sqlite3_snapshot_cmp`
|
|
||||||
- [ ] `sqlite3_snapshot_recover`
|
|
||||||
|
|
||||||
- [X] `sqlite3_serialize`
|
|
||||||
- [X] `sqlite3_deserialize`
|
|
||||||
|
|
||||||
- [ ] `sqlite3_rtree_geometry_callback`
|
|
||||||
- [ ] `sqlite3_rtree_query_callback`
|
|
||||||
|
|
||||||
- [X] `sqlite3session_create`
|
|
||||||
- [X] `sqlite3session_delete`
|
|
||||||
- [ ] `sqlite3session_object_config`
|
|
||||||
- [X] `sqlite3session_enable`
|
|
||||||
- [X] `sqlite3session_indirect`
|
|
||||||
- [X] `sqlite3session_attach`
|
|
||||||
- [X] `sqlite3session_table_filter` (Boxed callback, reference kept)
|
|
||||||
- [X] `sqlite3session_changeset`
|
|
||||||
- [ ] `sqlite3session_changeset_size`
|
|
||||||
- [X] `sqlite3session_diff`
|
|
||||||
- [X] `sqlite3session_patchset`
|
|
||||||
- [X] `sqlite3session_isempty`
|
|
||||||
- [ ] `sqlite3session_memory_used`
|
|
||||||
- [X] `sqlite3changeset_start`
|
|
||||||
- [ ] `sqlite3changeset_start_v2`
|
|
||||||
- [X] `sqlite3changeset_next`
|
|
||||||
- [X] `sqlite3changeset_op`
|
|
||||||
- [X] `sqlite3changeset_pk`
|
|
||||||
- [X] `sqlite3changeset_old`
|
|
||||||
- [X] `sqlite3changeset_new`
|
|
||||||
- [X] `sqlite3changeset_conflict`
|
|
||||||
- [X] `sqlite3changeset_fk_conflicts`
|
|
||||||
- [X] `sqlite3changeset_finalize`
|
|
||||||
- [X] `sqlite3changeset_invert`
|
|
||||||
- [X] `sqlite3changeset_concat`
|
|
||||||
- [ ] `sqlite3changeset_upgrade`
|
|
||||||
- [X] `sqlite3changegroup_new`
|
|
||||||
- [ ] `sqlite3changegroup_schema`
|
|
||||||
- [X] `sqlite3changegroup_add`
|
|
||||||
- [ ] `sqlite3changegroup_add_change`
|
|
||||||
- [X] `sqlite3changegroup_output`
|
|
||||||
- [X] `sqlite3changegroup_delete`
|
|
||||||
- [X] `sqlite3changeset_apply`
|
|
||||||
- [ ] `sqlite3changeset_apply_v2`
|
|
||||||
- [ ] `sqlite3rebaser_create`
|
|
||||||
- [ ] `sqlite3rebaser_configure`
|
|
||||||
- [ ] `sqlite3rebaser_rebase`
|
|
||||||
- [ ] `sqlite3rebaser_delete`
|
|
||||||
- [X] `sqlite3changeset_apply_strm`
|
|
||||||
- [ ] `sqlite3changeset_apply_v2_strm`
|
|
||||||
- [X] `sqlite3changeset_concat_strm`
|
|
||||||
- [X] `sqlite3changeset_invert_strm`
|
|
||||||
- [X] `sqlite3changeset_start_strm`
|
|
||||||
- [ ] `sqlite3changeset_start_v2_strm`
|
|
||||||
- [X] `sqlite3session_changeset_strm`
|
|
||||||
- [X] `sqlite3session_patchset_strm`
|
|
||||||
- [X] `sqlite3changegroup_add_strm`
|
|
||||||
- [X] `sqlite3changegroup_add_strm`
|
|
||||||
- [X] `sqlite3changegroup_output_strm`
|
|
||||||
- [ ] `sqlite3rebaser_rebase_strm`
|
|
||||||
- [ ] `sqlite3session_config`
|
|
||||||
|
|
||||||
## List of virtual table methods supported
|
|
||||||
|
|
||||||
- [X] `xCreate`
|
|
||||||
- [X] `xConnect`
|
|
||||||
- [X] `xBestIndex`
|
|
||||||
- [X] `xDisconnect`
|
|
||||||
- [X] `xDestroy`
|
|
||||||
- [X] `xOpen`
|
|
||||||
- [X] `xClose`
|
|
||||||
- [X] `xFilter`
|
|
||||||
- [X] `xNext`
|
|
||||||
- [X] `xEof`
|
|
||||||
- [X] `xColumn`
|
|
||||||
- [X] `xRowid`
|
|
||||||
- [X] `xUpdate`
|
|
||||||
- [X] `xBegin`
|
|
||||||
- [X] `xSync`
|
|
||||||
- [X] `xCommit`
|
|
||||||
- [X] `xRollback`
|
|
||||||
- [ ] `xFindFunction`
|
|
||||||
- [ ] `xRename`
|
|
||||||
- [ ] `xSavepoint`
|
|
||||||
- [ ] `xRelease`
|
|
||||||
- [ ] `xRollbackTo`
|
|
||||||
- [ ] `xShadowName`
|
|
||||||
- [ ] `xIntegrity`
|
|
||||||
23
vendor/rusqlite/examples/load_extension.rs
vendored
23
vendor/rusqlite/examples/load_extension.rs
vendored
@@ -1,23 +0,0 @@
|
|||||||
//! Ensure `loadable_extension.rs` works.
|
|
||||||
|
|
||||||
use rusqlite::{Connection, Result};
|
|
||||||
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
db.load_extension_enable()?;
|
|
||||||
db.load_extension(
|
|
||||||
format!("target/debug/examples/{DLL_PREFIX}loadable_extension{DLL_SUFFIX}"),
|
|
||||||
None::<&str>,
|
|
||||||
)?;
|
|
||||||
db.load_extension_disable()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let str = db.query_row("SELECT rusqlite_test_function()", [], |row| {
|
|
||||||
row.get::<_, String>(0)
|
|
||||||
})?;
|
|
||||||
assert_eq!(&str, "Rusqlite extension loaded correctly!");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
49
vendor/rusqlite/examples/loadable_extension.rs
vendored
49
vendor/rusqlite/examples/loadable_extension.rs
vendored
@@ -1,49 +0,0 @@
|
|||||||
//! Adaptation of https://sqlite.org/loadext.html#programming_loadable_extensions
|
|
||||||
//!
|
|
||||||
//! # build
|
|
||||||
//! ```sh
|
|
||||||
//! cargo build --example loadable_extension --features "loadable_extension functions trace"
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # test
|
|
||||||
//! ```sh
|
|
||||||
//! sqlite> .log on
|
|
||||||
//! sqlite> .load target/debug/examples/libloadable_extension.so
|
|
||||||
//! (28) Rusqlite extension initialized
|
|
||||||
//! sqlite> SELECT rusqlite_test_function();
|
|
||||||
//! Rusqlite extension loaded correctly!
|
|
||||||
//! ```
|
|
||||||
use std::os::raw::{c_char, c_int};
|
|
||||||
|
|
||||||
use rusqlite::ffi;
|
|
||||||
use rusqlite::functions::FunctionFlags;
|
|
||||||
use rusqlite::types::{ToSqlOutput, Value};
|
|
||||||
use rusqlite::{Connection, Result};
|
|
||||||
|
|
||||||
/// Entry point for SQLite to load the extension.
|
|
||||||
/// See <https://sqlite.org/c3ref/load_extension.html> on this function's name and usage.
|
|
||||||
/// # Safety
|
|
||||||
/// This function is called by SQLite and must be safe to call.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn sqlite3_extension_init(
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
pz_err_msg: *mut *mut c_char,
|
|
||||||
p_api: *mut ffi::sqlite3_api_routines,
|
|
||||||
) -> c_int {
|
|
||||||
Connection::extension_init2(db, pz_err_msg, p_api, extension_init)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extension_init(db: Connection) -> Result<bool> {
|
|
||||||
db.create_scalar_function(
|
|
||||||
c"rusqlite_test_function",
|
|
||||||
0,
|
|
||||||
FunctionFlags::SQLITE_DETERMINISTIC,
|
|
||||||
|_ctx| {
|
|
||||||
Ok(ToSqlOutput::Owned(Value::Text(
|
|
||||||
"Rusqlite extension loaded correctly!".to_string(),
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
rusqlite::trace::log(ffi::SQLITE_WARNING, "Rusqlite extension initialized");
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
27
vendor/rusqlite/examples/owning_rows.rs
vendored
27
vendor/rusqlite/examples/owning_rows.rs
vendored
@@ -1,27 +0,0 @@
|
|||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
use rusqlite::{CachedStatement, Connection, Result, Rows};
|
|
||||||
use self_cell::{self_cell, MutBorrow};
|
|
||||||
|
|
||||||
type RowsRef<'a> = Rows<'a>;
|
|
||||||
|
|
||||||
self_cell!(
|
|
||||||
struct OwningRows<'conn> {
|
|
||||||
owner: MutBorrow<CachedStatement<'conn>>,
|
|
||||||
#[covariant]
|
|
||||||
dependent: RowsRef,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
let stmt = conn.prepare_cached("SELECT 1")?;
|
|
||||||
let mut or = OwningRows::try_new(MutBorrow::new(stmt), |s| s.borrow_mut().query([]))?;
|
|
||||||
or.with_dependent_mut(|_stmt, rows| -> Result<()> {
|
|
||||||
while let Some(row) = rows.next()? {
|
|
||||||
assert_eq!(Ok(1), row.get(0));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
30
vendor/rusqlite/examples/owning_statement.rs
vendored
30
vendor/rusqlite/examples/owning_statement.rs
vendored
@@ -1,30 +0,0 @@
|
|||||||
extern crate rusqlite;
|
|
||||||
use rusqlite::{CachedStatement, Connection, Result, Rows};
|
|
||||||
use self_cell::{self_cell, MutBorrow};
|
|
||||||
|
|
||||||
type CachedStatementRef<'a> = CachedStatement<'a>;
|
|
||||||
|
|
||||||
// Caveat: single statement at a time for one connection.
|
|
||||||
// But if you need multiple statements, you can still create your own struct
|
|
||||||
// with multiple fields (one for each statement).
|
|
||||||
self_cell!(
|
|
||||||
struct OwningStatement {
|
|
||||||
owner: MutBorrow<Connection>,
|
|
||||||
#[covariant]
|
|
||||||
dependent: CachedStatementRef,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
let mut os = OwningStatement::try_new(MutBorrow::new(conn), |s| {
|
|
||||||
s.borrow_mut().prepare_cached("SELECT 1")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut rows = os.with_dependent_mut(|_conn, stmt| -> Result<Rows<'_>> { stmt.query([]) })?;
|
|
||||||
while let Some(row) = rows.next()? {
|
|
||||||
assert_eq!(Ok(1), row.get(0));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
48
vendor/rusqlite/examples/persons/README.md
vendored
48
vendor/rusqlite/examples/persons/README.md
vendored
@@ -1,48 +0,0 @@
|
|||||||
# Persons example
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cargo run --example persons
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run (wasm32-wasi)
|
|
||||||
|
|
||||||
### Requisites
|
|
||||||
|
|
||||||
- [wasi-sdk](https://github.com/WebAssembly/wasi-sdk)
|
|
||||||
- [wasmtime](https://wasmtime.dev/)
|
|
||||||
|
|
||||||
```
|
|
||||||
# Set to wasi-sdk directory
|
|
||||||
$ export WASI_SDK_PATH=`<wasi-sdk-path>`
|
|
||||||
$ export CC_wasm32_wasi="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
|
|
||||||
# Build
|
|
||||||
$ cargo build --example persons --target wasm32-wasi --release --features bundled
|
|
||||||
# Run
|
|
||||||
$ wasmtime target/wasm32-wasi/release/examples/persons.wasm
|
|
||||||
Found persons:
|
|
||||||
ID: 1, Name: Steven
|
|
||||||
ID: 2, Name: John
|
|
||||||
ID: 3, Name: Alex
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run (wasm32-unknown-unknown)
|
|
||||||
|
|
||||||
### Requisites
|
|
||||||
|
|
||||||
- [emscripten](https://emscripten.org/docs/getting_started/downloads.html)
|
|
||||||
- [wasm-bindgen-cli](https://github.com/wasm-bindgen/wasm-bindgen)
|
|
||||||
|
|
||||||
```
|
|
||||||
# Build
|
|
||||||
$ cargo build --example persons --target wasm32-unknown-unknown --release
|
|
||||||
# Bindgen
|
|
||||||
$ wasm-bindgen target/wasm32-unknown-unknown/release/examples/persons.wasm --out-dir target/pkg --nodejs
|
|
||||||
# Run
|
|
||||||
$ node target/pkg/persons.js
|
|
||||||
Found persons:
|
|
||||||
ID: 1, Name: Steven
|
|
||||||
ID: 2, Name: John
|
|
||||||
ID: 3, Name: Alex
|
|
||||||
```
|
|
||||||
57
vendor/rusqlite/examples/persons/main.rs
vendored
57
vendor/rusqlite/examples/persons/main.rs
vendored
@@ -1,57 +0,0 @@
|
|||||||
use rusqlite::{Connection, Result};
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
|
||||||
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
#[wasm_bindgen]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen(js_namespace = console)]
|
|
||||||
fn log(s: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
macro_rules! println {
|
|
||||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Person {
|
|
||||||
id: i32,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(all(target_family = "wasm", target_os = "unknown"), wasm_bindgen(main))]
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
conn.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS persons (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL
|
|
||||||
)",
|
|
||||||
(), // empty list of parameters.
|
|
||||||
)?;
|
|
||||||
|
|
||||||
conn.execute(
|
|
||||||
"INSERT INTO persons (name) VALUES (?1), (?2), (?3)",
|
|
||||||
["Steven", "John", "Alex"].map(|n| n.to_string()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut stmt = conn.prepare("SELECT id, name FROM persons")?;
|
|
||||||
let rows = stmt.query_map([], |row| {
|
|
||||||
Ok(Person {
|
|
||||||
id: row.get(0)?,
|
|
||||||
name: row.get(1)?,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
println!("Found persons:");
|
|
||||||
|
|
||||||
for person in rows {
|
|
||||||
match person {
|
|
||||||
Ok(p) => println!("ID: {}, Name: {}", p.id, p.name),
|
|
||||||
Err(e) => eprintln!("Error: {e:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
62
vendor/rusqlite/src/auto_extension.rs
vendored
62
vendor/rusqlite/src/auto_extension.rs
vendored
@@ -1,62 +0,0 @@
|
|||||||
//! Automatic extension loading
|
|
||||||
use super::ffi;
|
|
||||||
use crate::error::{check, to_sqlite_error};
|
|
||||||
use crate::{Connection, Error, Result};
|
|
||||||
use std::ffi::{c_char, c_int};
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
|
|
||||||
/// Automatic extension initialization routine
|
|
||||||
pub type AutoExtension = fn(Connection) -> Result<()>;
|
|
||||||
|
|
||||||
/// Raw automatic extension initialization routine
|
|
||||||
pub type RawAutoExtension = unsafe extern "C" fn(
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
pz_err_msg: *mut *mut c_char,
|
|
||||||
_: *const ffi::sqlite3_api_routines,
|
|
||||||
) -> c_int;
|
|
||||||
|
|
||||||
/// Bridge between `RawAutoExtension` and `AutoExtension`
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// * Opening a database from an auto-extension handler will lead to
|
|
||||||
/// an endless recursion of the auto-handler triggering itself
|
|
||||||
/// indirectly for each newly-opened database.
|
|
||||||
/// * Results are undefined if the given db is closed by an auto-extension.
|
|
||||||
/// * The list of auto-extensions should not be manipulated from an auto-extension.
|
|
||||||
pub unsafe fn init_auto_extension(
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
pz_err_msg: *mut *mut c_char,
|
|
||||||
ax: AutoExtension,
|
|
||||||
) -> c_int {
|
|
||||||
let r = catch_unwind(|| {
|
|
||||||
let c = Connection::from_handle(db);
|
|
||||||
c.and_then(ax)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|_| Err(Error::UnwindingPanic));
|
|
||||||
match r {
|
|
||||||
Err(e) => to_sqlite_error(&e, pz_err_msg),
|
|
||||||
_ => ffi::SQLITE_OK,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register au auto-extension
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// * Opening a database from an auto-extension handler will lead to
|
|
||||||
/// an endless recursion of the auto-handler triggering itself
|
|
||||||
/// indirectly for each newly-opened database.
|
|
||||||
/// * Results are undefined if the given db is closed by an auto-extension.
|
|
||||||
/// * The list of auto-extensions should not be manipulated from an auto-extension.
|
|
||||||
pub unsafe fn register_auto_extension(ax: RawAutoExtension) -> Result<()> {
|
|
||||||
check(ffi::sqlite3_auto_extension(Some(ax)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unregister the initialization routine
|
|
||||||
pub fn cancel_auto_extension(ax: RawAutoExtension) -> bool {
|
|
||||||
unsafe { ffi::sqlite3_cancel_auto_extension(Some(ax)) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Disable all automatic extensions previously registered
|
|
||||||
pub fn reset_auto_extension() {
|
|
||||||
unsafe { ffi::sqlite3_reset_auto_extension() }
|
|
||||||
}
|
|
||||||
442
vendor/rusqlite/src/backup.rs
vendored
442
vendor/rusqlite/src/backup.rs
vendored
@@ -1,442 +0,0 @@
|
|||||||
//! Online SQLite backup API.
|
|
||||||
//!
|
|
||||||
//! Alternatively, you can create a backup with a simple
|
|
||||||
//! [`VACUUM INTO <backup_path>`](https://sqlite.org/lang_vacuum.html#vacuuminto).
|
|
||||||
//!
|
|
||||||
//! To create a [`Backup`], you must have two distinct [`Connection`]s - one
|
|
||||||
//! for the source (which can be used while the backup is running) and one for
|
|
||||||
//! the destination (which cannot). A [`Backup`] handle exposes three methods:
|
|
||||||
//! [`step`](Backup::step) will attempt to back up a specified number of pages,
|
|
||||||
//! [`progress`](Backup::progress) gets the current progress of the backup as of
|
|
||||||
//! the last call to [`step`](Backup::step), and
|
|
||||||
//! [`run_to_completion`](Backup::run_to_completion) will attempt to back up the
|
|
||||||
//! entire source database, allowing you to specify how many pages are backed up
|
|
||||||
//! at a time and how long the thread should sleep between chunks of pages.
|
|
||||||
//!
|
|
||||||
//! The following example is equivalent to "Example 2: Online Backup of a
|
|
||||||
//! Running Database" from [SQLite's Online Backup API
|
|
||||||
//! documentation](https://www.sqlite.org/backup.html).
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! # use rusqlite::{backup, Connection, Result};
|
|
||||||
//! # use std::path::Path;
|
|
||||||
//! # use std::time;
|
|
||||||
//!
|
|
||||||
//! fn backup_db<P: AsRef<Path>>(
|
|
||||||
//! src: &Connection,
|
|
||||||
//! dst: P,
|
|
||||||
//! progress: fn(backup::Progress),
|
|
||||||
//! ) -> Result<()> {
|
|
||||||
//! let mut dst = Connection::open(dst)?;
|
|
||||||
//! let backup = backup::Backup::new(src, &mut dst)?;
|
|
||||||
//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
|
|
||||||
use crate::error::error_from_handle;
|
|
||||||
use crate::{Connection, Name, Result, MAIN_DB};
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Back up the `name` database to the given
|
|
||||||
/// destination path.
|
|
||||||
///
|
|
||||||
/// If `progress` is not `None`, it will be called periodically
|
|
||||||
/// until the backup completes.
|
|
||||||
///
|
|
||||||
/// For more fine-grained control over the backup process (e.g.,
|
|
||||||
/// to sleep periodically during the backup or to back up to an
|
|
||||||
/// already-open database connection), see the `backup` module.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the destination path cannot be opened
|
|
||||||
/// or if the backup fails.
|
|
||||||
pub fn backup<N: Name, P: AsRef<Path>>(
|
|
||||||
&self,
|
|
||||||
name: N,
|
|
||||||
dst_path: P,
|
|
||||||
progress: Option<fn(Progress)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
use self::StepResult::{Busy, Done, Locked, More};
|
|
||||||
let mut dst = Self::open(dst_path)?;
|
|
||||||
let backup = Backup::new_with_names(self, name, &mut dst, MAIN_DB)?;
|
|
||||||
|
|
||||||
let mut r = More;
|
|
||||||
while r == More {
|
|
||||||
r = backup.step(100)?;
|
|
||||||
if let Some(f) = progress {
|
|
||||||
f(backup.progress());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match r {
|
|
||||||
Done => Ok(()),
|
|
||||||
Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }),
|
|
||||||
Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }),
|
|
||||||
More => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restore the given source path into the
|
|
||||||
/// `name` database. If `progress` is not `None`, it will be
|
|
||||||
/// called periodically until the restore completes.
|
|
||||||
///
|
|
||||||
/// For more fine-grained control over the restore process (e.g.,
|
|
||||||
/// to sleep periodically during the restore or to restore from an
|
|
||||||
/// already-open database connection), see the `backup` module.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the destination path cannot be opened
|
|
||||||
/// or if the restore fails.
|
|
||||||
pub fn restore<N: Name, P: AsRef<Path>, F: Fn(Progress)>(
|
|
||||||
&mut self,
|
|
||||||
name: N,
|
|
||||||
src_path: P,
|
|
||||||
progress: Option<F>,
|
|
||||||
) -> Result<()> {
|
|
||||||
use self::StepResult::{Busy, Done, Locked, More};
|
|
||||||
let src = Self::open(src_path)?;
|
|
||||||
let restore = Backup::new_with_names(&src, MAIN_DB, self, name)?;
|
|
||||||
|
|
||||||
let mut r = More;
|
|
||||||
let mut busy_count = 0_i32;
|
|
||||||
'restore_loop: while r == More || r == Busy {
|
|
||||||
r = restore.step(100)?;
|
|
||||||
if let Some(ref f) = progress {
|
|
||||||
f(restore.progress());
|
|
||||||
}
|
|
||||||
if r == Busy {
|
|
||||||
busy_count += 1;
|
|
||||||
if busy_count >= 3 {
|
|
||||||
break 'restore_loop;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match r {
|
|
||||||
Done => Ok(()),
|
|
||||||
Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }),
|
|
||||||
Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }),
|
|
||||||
More => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Possible successful results of calling
|
|
||||||
/// [`Backup::step`].
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum StepResult {
|
|
||||||
/// The backup is complete.
|
|
||||||
Done,
|
|
||||||
|
|
||||||
/// The step was successful but there are still more pages that need to be
|
|
||||||
/// backed up.
|
|
||||||
More,
|
|
||||||
|
|
||||||
/// The step failed because appropriate locks could not be acquired. This is
|
|
||||||
/// not a fatal error - the step can be retried.
|
|
||||||
Busy,
|
|
||||||
|
|
||||||
/// The step failed because the source connection was writing to the
|
|
||||||
/// database. This is not a fatal error - the step can be retried.
|
|
||||||
Locked,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Struct specifying the progress of a backup.
|
|
||||||
///
|
|
||||||
/// The percentage completion can be calculated as `(pagecount - remaining) /
|
|
||||||
/// pagecount`. The progress of a backup is as of the last call to
|
|
||||||
/// [`step`](Backup::step) - if the source database is modified after a call to
|
|
||||||
/// [`step`](Backup::step), the progress value will become outdated and
|
|
||||||
/// potentially incorrect.
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct Progress {
|
|
||||||
/// Number of pages in the source database that still need to be backed up.
|
|
||||||
pub remaining: c_int,
|
|
||||||
/// Total number of pages in the source database.
|
|
||||||
pub pagecount: c_int,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to an online backup.
|
|
||||||
pub struct Backup<'a, 'b> {
|
|
||||||
phantom_from: PhantomData<&'a Connection>,
|
|
||||||
to: &'b Connection,
|
|
||||||
b: *mut ffi::sqlite3_backup,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Backup<'_, '_> {
|
|
||||||
/// Attempt to create a new handle that will allow backups from `from` to
|
|
||||||
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
|
|
||||||
/// API calls on the destination of a backup while the backup is taking
|
|
||||||
/// place.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
|
||||||
/// `NULL`.
|
|
||||||
#[inline]
|
|
||||||
pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
|
|
||||||
Backup::new_with_names(from, MAIN_DB, to, MAIN_DB)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to create a new handle that will allow backups from the
|
|
||||||
/// `from_name` database of `from` to the `to_name` database of `to`. Note
|
|
||||||
/// that `to` is a `&mut` - this is because SQLite forbids any API calls on
|
|
||||||
/// the destination of a backup while the backup is taking place.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
|
||||||
/// `NULL`.
|
|
||||||
pub fn new_with_names<'a, 'b, F: Name, T: Name>(
|
|
||||||
from: &'a Connection,
|
|
||||||
from_name: F,
|
|
||||||
to: &'b mut Connection,
|
|
||||||
to_name: T,
|
|
||||||
) -> Result<Backup<'a, 'b>> {
|
|
||||||
let to_name = to_name.as_cstr()?;
|
|
||||||
let from_name = from_name.as_cstr()?;
|
|
||||||
|
|
||||||
let to_db = to.db.borrow_mut().db;
|
|
||||||
|
|
||||||
let b = unsafe {
|
|
||||||
let b = ffi::sqlite3_backup_init(
|
|
||||||
to_db,
|
|
||||||
to_name.as_ptr(),
|
|
||||||
from.db.borrow_mut().db,
|
|
||||||
from_name.as_ptr(),
|
|
||||||
);
|
|
||||||
if b.is_null() {
|
|
||||||
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
|
|
||||||
}
|
|
||||||
b
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Backup {
|
|
||||||
phantom_from: PhantomData,
|
|
||||||
to,
|
|
||||||
b,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the progress of the backup as of the last call to
|
|
||||||
/// [`step`](Backup::step).
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn progress(&self) -> Progress {
|
|
||||||
unsafe {
|
|
||||||
Progress {
|
|
||||||
remaining: ffi::sqlite3_backup_remaining(self.b),
|
|
||||||
pagecount: ffi::sqlite3_backup_pagecount(self.b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to back up the given number of pages. If `num_pages` is
|
|
||||||
/// negative, will attempt to back up all remaining pages. This will hold a
|
|
||||||
/// lock on the source database for the duration, so it is probably not
|
|
||||||
/// what you want for databases that are currently active (see
|
|
||||||
/// [`run_to_completion`](Backup::run_to_completion) for a better
|
|
||||||
/// alternative).
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_step` call returns
|
|
||||||
/// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
|
|
||||||
/// `LOCKED` are transient errors and are therefore returned as possible
|
|
||||||
/// `Ok` values.
|
|
||||||
#[inline]
|
|
||||||
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
|
|
||||||
use self::StepResult::{Busy, Done, Locked, More};
|
|
||||||
|
|
||||||
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
|
|
||||||
match rc {
|
|
||||||
ffi::SQLITE_DONE => Ok(Done),
|
|
||||||
ffi::SQLITE_OK => Ok(More),
|
|
||||||
ffi::SQLITE_BUSY => Ok(Busy),
|
|
||||||
ffi::SQLITE_LOCKED => Ok(Locked),
|
|
||||||
_ => self.to.decode_result(rc).map(|_| More),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to run the entire backup. Will call
|
|
||||||
/// [`step(pages_per_step)`](Backup::step) as many times as necessary,
|
|
||||||
/// sleeping for `pause_between_pages` between each call to give the
|
|
||||||
/// source database time to process any pending queries. This is a
|
|
||||||
/// direct implementation of "Example 2: Online Backup of a Running
|
|
||||||
/// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html).
|
|
||||||
///
|
|
||||||
/// If `progress` is not `None`, it will be called after each step with the
|
|
||||||
/// current progress of the backup. Note that is possible the progress may
|
|
||||||
/// not change if the step returns `Busy` or `Locked` even though the
|
|
||||||
/// backup is still running.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if any of the calls to [`step`](Backup::step) return
|
|
||||||
/// `Err`.
|
|
||||||
pub fn run_to_completion(
|
|
||||||
&self,
|
|
||||||
pages_per_step: c_int,
|
|
||||||
pause_between_pages: Duration,
|
|
||||||
progress: Option<fn(Progress)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
use self::StepResult::{Busy, Done, Locked, More};
|
|
||||||
|
|
||||||
assert!(pages_per_step > 0, "pages_per_step must be positive");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let r = self.step(pages_per_step)?;
|
|
||||||
if let Some(progress) = progress {
|
|
||||||
progress(self.progress());
|
|
||||||
}
|
|
||||||
match r {
|
|
||||||
More | Busy | Locked => thread::sleep(pause_between_pages),
|
|
||||||
Done => return Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Backup<'_, '_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { ffi::sqlite3_backup_finish(self.b) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::{Backup, Progress};
|
|
||||||
use crate::{Connection, Result, MAIN_DB, TEMP_DB};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
all(target_family = "wasm", target_os = "unknown"),
|
|
||||||
ignore = "no filesystem on this platform"
|
|
||||||
)]
|
|
||||||
#[test]
|
|
||||||
fn backup_to_path() -> Result<()> {
|
|
||||||
let src = Connection::open_in_memory()?;
|
|
||||||
src.execute_batch("CREATE TABLE foo AS SELECT 42 AS x")?;
|
|
||||||
|
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
|
||||||
let path = temp_dir.path().join("test.db3");
|
|
||||||
|
|
||||||
fn progress(_: Progress) {}
|
|
||||||
|
|
||||||
src.backup(MAIN_DB, path.as_path(), Some(progress))?;
|
|
||||||
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
dst.restore(MAIN_DB, path, Some(progress))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_backup() -> Result<()> {
|
|
||||||
let src = Connection::open_in_memory()?;
|
|
||||||
let sql = "BEGIN;
|
|
||||||
CREATE TABLE foo(x INTEGER);
|
|
||||||
INSERT INTO foo VALUES(42);
|
|
||||||
END;";
|
|
||||||
src.execute_batch(sql)?;
|
|
||||||
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let backup = Backup::new(&src, &mut dst)?;
|
|
||||||
backup.step(-1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(42, dst.one_column::<i64, _>("SELECT x FROM foo", [])?);
|
|
||||||
|
|
||||||
src.execute_batch("INSERT INTO foo VALUES(43)")?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let backup = Backup::new(&src, &mut dst)?;
|
|
||||||
backup.run_to_completion(5, Duration::from_millis(250), None)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?;
|
|
||||||
assert_eq!(42 + 43, the_answer);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_backup_temp() -> Result<()> {
|
|
||||||
let src = Connection::open_in_memory()?;
|
|
||||||
let sql = "BEGIN;
|
|
||||||
CREATE TEMPORARY TABLE foo(x INTEGER);
|
|
||||||
INSERT INTO foo VALUES(42);
|
|
||||||
END;";
|
|
||||||
src.execute_batch(sql)?;
|
|
||||||
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let backup = Backup::new_with_names(&src, TEMP_DB, &mut dst, MAIN_DB)?;
|
|
||||||
backup.step(-1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(42, dst.one_column::<i64, _>("SELECT x FROM foo", [])?);
|
|
||||||
|
|
||||||
src.execute_batch("INSERT INTO foo VALUES(43)")?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let backup = Backup::new_with_names(&src, TEMP_DB, &mut dst, MAIN_DB)?;
|
|
||||||
backup.run_to_completion(5, Duration::from_millis(250), None)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?;
|
|
||||||
assert_eq!(42 + 43, the_answer);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_backup_attached() -> Result<()> {
|
|
||||||
let src = Connection::open_in_memory()?;
|
|
||||||
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
|
|
||||||
BEGIN;
|
|
||||||
CREATE TABLE my_attached.foo(x INTEGER);
|
|
||||||
INSERT INTO my_attached.foo VALUES(42);
|
|
||||||
END;";
|
|
||||||
src.execute_batch(sql)?;
|
|
||||||
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let backup = Backup::new_with_names(&src, c"my_attached", &mut dst, MAIN_DB)?;
|
|
||||||
backup.step(-1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(42, dst.one_column::<i64, _>("SELECT x FROM foo", [])?);
|
|
||||||
|
|
||||||
src.execute_batch("INSERT INTO foo VALUES(43)")?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let backup = Backup::new_with_names(&src, c"my_attached", &mut dst, MAIN_DB)?;
|
|
||||||
backup.run_to_completion(5, Duration::from_millis(250), None)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?;
|
|
||||||
assert_eq!(42 + 43, the_answer);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
71
vendor/rusqlite/src/bind.rs
vendored
71
vendor/rusqlite/src/bind.rs
vendored
@@ -1,71 +0,0 @@
|
|||||||
use crate::{ffi, Error, Result, Statement};
|
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
mod sealed {
|
|
||||||
use std::ffi::CStr;
|
|
||||||
/// This trait exists just to ensure that the only impls of `trait BindIndex`
|
|
||||||
/// that are allowed are ones in this crate.
|
|
||||||
pub trait Sealed {}
|
|
||||||
impl Sealed for usize {}
|
|
||||||
impl Sealed for &str {}
|
|
||||||
impl Sealed for &CStr {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait implemented by types that can index into parameters of a statement.
|
|
||||||
///
|
|
||||||
/// It is only implemented for `usize` and `&str` and `&CStr`.
|
|
||||||
pub trait BindIndex: sealed::Sealed {
|
|
||||||
/// Returns the index of the associated parameter, or `Error` if no such
|
|
||||||
/// parameter exists.
|
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BindIndex for usize {
|
|
||||||
#[inline]
|
|
||||||
fn idx(&self, _: &Statement<'_>) -> Result<usize> {
|
|
||||||
// No validation
|
|
||||||
Ok(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BindIndex for &'_ str {
|
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
|
||||||
match stmt.parameter_index(self)? {
|
|
||||||
Some(idx) => Ok(idx),
|
|
||||||
None => Err(Error::InvalidParameterName(self.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// C-string literal to avoid alloc
|
|
||||||
impl BindIndex for &CStr {
|
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
|
||||||
let r = unsafe { ffi::sqlite3_bind_parameter_index(stmt.ptr(), self.as_ptr()) };
|
|
||||||
match r {
|
|
||||||
0 => Err(Error::InvalidParameterName(
|
|
||||||
self.to_string_lossy().to_string(),
|
|
||||||
)),
|
|
||||||
i => Ok(i as usize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::{ffi, Connection, Error, Result};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_name() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let mut stmt = db.prepare("SELECT 1")?;
|
|
||||||
let err = stmt.raw_bind_parameter(1, 1).unwrap_err();
|
|
||||||
assert_eq!(
|
|
||||||
err.sqlite_error_code(),
|
|
||||||
Some(ffi::ErrorCode::ParameterOutOfRange),
|
|
||||||
);
|
|
||||||
let err = stmt.raw_bind_parameter(":p1", 1).unwrap_err();
|
|
||||||
assert_eq!(err, Error::InvalidParameterName(":p1".to_owned()));
|
|
||||||
let err = stmt.raw_bind_parameter(c"x", 1).unwrap_err();
|
|
||||||
assert_eq!(err, Error::InvalidParameterName("x".to_owned()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
564
vendor/rusqlite/src/blob/mod.rs
vendored
564
vendor/rusqlite/src/blob/mod.rs
vendored
@@ -1,564 +0,0 @@
|
|||||||
//! Incremental BLOB I/O.
|
|
||||||
//!
|
|
||||||
//! Note that SQLite does not provide API-level access to change the size of a
|
|
||||||
//! BLOB; that must be performed through SQL statements.
|
|
||||||
//!
|
|
||||||
//! There are two choices for how to perform IO on a [`Blob`].
|
|
||||||
//!
|
|
||||||
//! 1. The implementations it provides of the `std::io::Read`, `std::io::Write`,
|
|
||||||
//! and `std::io::Seek` traits.
|
|
||||||
//!
|
|
||||||
//! 2. A positional IO API, e.g. [`Blob::read_at`], [`Blob::write_at`] and
|
|
||||||
//! similar.
|
|
||||||
//!
|
|
||||||
//! Documenting these in order:
|
|
||||||
//!
|
|
||||||
//! ## 1. `std::io` trait implementations.
|
|
||||||
//!
|
|
||||||
//! `Blob` conforms to `std::io::Read`, `std::io::Write`, and `std::io::Seek`,
|
|
||||||
//! so it plays nicely with other types that build on these (such as
|
|
||||||
//! `std::io::BufReader` and `std::io::BufWriter`). However, you must be careful
|
|
||||||
//! with the size of the blob. For example, when using a `BufWriter`, the
|
|
||||||
//! `BufWriter` will accept more data than the `Blob` will allow, so make sure
|
|
||||||
//! to call `flush` and check for errors. (See the unit tests in this module for
|
|
||||||
//! an example.)
|
|
||||||
//!
|
|
||||||
//! ## 2. Positional IO
|
|
||||||
//!
|
|
||||||
//! `Blob`s also offer a `pread` / `pwrite`-style positional IO api in the form
|
|
||||||
//! of [`Blob::read_at`], [`Blob::write_at`], [`Blob::raw_read_at`],
|
|
||||||
//! [`Blob::read_at_exact`], and [`Blob::raw_read_at_exact`].
|
|
||||||
//!
|
|
||||||
//! These APIs all take the position to read from or write to from as a
|
|
||||||
//! parameter, instead of using an internal `pos` value.
|
|
||||||
//!
|
|
||||||
//! ### Positional IO Read Variants
|
|
||||||
//!
|
|
||||||
//! For the `read` functions, there are several functions provided:
|
|
||||||
//!
|
|
||||||
//! - [`Blob::read_at`]
|
|
||||||
//! - [`Blob::raw_read_at`]
|
|
||||||
//! - [`Blob::read_at_exact`]
|
|
||||||
//! - [`Blob::raw_read_at_exact`]
|
|
||||||
//!
|
|
||||||
//! These can be divided along two axes: raw/not raw, and exact/inexact:
|
|
||||||
//!
|
|
||||||
//! 1. Raw/not raw refers to the type of the destination buffer. The raw
|
|
||||||
//! functions take a `&mut [MaybeUninit<u8>]` as the destination buffer,
|
|
||||||
//! where the "normal" functions take a `&mut [u8]`.
|
|
||||||
//!
|
|
||||||
//! Using `MaybeUninit` here can be more efficient in some cases, but is
|
|
||||||
//! often inconvenient, so both are provided.
|
|
||||||
//!
|
|
||||||
//! 2. Exact/inexact refers to whether or not the entire buffer must be
|
|
||||||
//! filled in order for the call to be considered a success.
|
|
||||||
//!
|
|
||||||
//! The "exact" functions require the provided buffer be entirely filled, or
|
|
||||||
//! they return an error, whereas the "inexact" functions read as much out of
|
|
||||||
//! the blob as is available, and return how much they were able to read.
|
|
||||||
//!
|
|
||||||
//! The inexact functions are preferable if you do not know the size of the
|
|
||||||
//! blob already, and the exact functions are preferable if you do.
|
|
||||||
//!
|
|
||||||
//! ### Comparison to using the `std::io` traits:
|
|
||||||
//!
|
|
||||||
//! In general, the positional methods offer the following Pro/Cons compared to
|
|
||||||
//! using the implementation `std::io::{Read, Write, Seek}` we provide for
|
|
||||||
//! `Blob`:
|
|
||||||
//!
|
|
||||||
//! 1. (Pro) There is no need to first seek to a position in order to perform IO
|
|
||||||
//! on it as the position is a parameter.
|
|
||||||
//!
|
|
||||||
//! 2. (Pro) `Blob`'s positional read functions don't mutate the blob in any
|
|
||||||
//! way, and take `&self`. No `&mut` access required.
|
|
||||||
//!
|
|
||||||
//! 3. (Pro) Positional IO functions return `Err(rusqlite::Error)` on failure,
|
|
||||||
//! rather than `Err(std::io::Error)`. Returning `rusqlite::Error` is more
|
|
||||||
//! accurate and convenient.
|
|
||||||
//!
|
|
||||||
//! Note that for the `std::io` API, no data is lost however, and it can be
|
|
||||||
//! recovered with `io_err.downcast::<rusqlite::Error>()` (this can be easy
|
|
||||||
//! to forget, though).
|
|
||||||
//!
|
|
||||||
//! 4. (Pro, for now). A `raw` version of the read API exists which can allow
|
|
||||||
//! reading into a `&mut [MaybeUninit<u8>]` buffer, which avoids a potential
|
|
||||||
//! costly initialization step. (However, `std::io` traits will certainly
|
|
||||||
//! gain this someday, which is why this is only a "Pro, for now").
|
|
||||||
//!
|
|
||||||
//! 5. (Con) The set of functions is more bare-bones than what is offered in
|
|
||||||
//! `std::io`, which has a number of adapters, handy algorithms, further
|
|
||||||
//! traits.
|
|
||||||
//!
|
|
||||||
//! 6. (Con) No meaningful interoperability with other crates, so if you need
|
|
||||||
//! that you must use `std::io`.
|
|
||||||
//!
|
|
||||||
//! To generalize: the `std::io` traits are useful because they conform to a
|
|
||||||
//! standard interface that a lot of code knows how to handle, however that
|
|
||||||
//! interface is not a perfect fit for [`Blob`], so another small set of
|
|
||||||
//! functions is provided as well.
|
|
||||||
//!
|
|
||||||
//! # Example (`std::io`)
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use rusqlite::blob::ZeroBlob;
|
|
||||||
//! # use rusqlite::{Connection, MAIN_DB};
|
|
||||||
//! # use std::error::Error;
|
|
||||||
//! # use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
//! # fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
//! let db = Connection::open_in_memory()?;
|
|
||||||
//! db.execute_batch("CREATE TABLE test_table (content BLOB);")?;
|
|
||||||
//!
|
|
||||||
//! // Insert a BLOB into the `content` column of `test_table`. Note that the Blob
|
|
||||||
//! // I/O API provides no way of inserting or resizing BLOBs in the DB -- this
|
|
||||||
//! // must be done via SQL.
|
|
||||||
//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
|
|
||||||
//!
|
|
||||||
//! // Get the row id off the BLOB we just inserted.
|
|
||||||
//! let rowid = db.last_insert_rowid();
|
|
||||||
//! // Open the BLOB we just inserted for IO.
|
|
||||||
//! let mut blob = db.blob_open(MAIN_DB, "test_table", "content", rowid, false)?;
|
|
||||||
//!
|
|
||||||
//! // Write some data into the blob. Make sure to test that the number of bytes
|
|
||||||
//! // written matches what you expect; if you try to write too much, the data
|
|
||||||
//! // will be truncated to the size of the BLOB.
|
|
||||||
//! let bytes_written = blob.write(b"01234567")?;
|
|
||||||
//! assert_eq!(bytes_written, 8);
|
|
||||||
//!
|
|
||||||
//! // Move back to the start and read into a local buffer.
|
|
||||||
//! // Same guidance - make sure you check the number of bytes read!
|
|
||||||
//! blob.seek(SeekFrom::Start(0))?;
|
|
||||||
//! let mut buf = [0u8; 20];
|
|
||||||
//! let bytes_read = blob.read(&mut buf[..])?;
|
|
||||||
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
|
|
||||||
//!
|
|
||||||
//! // Insert another BLOB, this time using a parameter passed in from
|
|
||||||
//! // rust (potentially with a dynamic size).
|
|
||||||
//! db.execute(
|
|
||||||
//! "INSERT INTO test_table (content) VALUES (?1)",
|
|
||||||
//! [ZeroBlob(64)],
|
|
||||||
//! )?;
|
|
||||||
//!
|
|
||||||
//! // given a new row ID, we can reopen the blob on that row
|
|
||||||
//! let rowid = db.last_insert_rowid();
|
|
||||||
//! blob.reopen(rowid)?;
|
|
||||||
//! // Just check that the size is right.
|
|
||||||
//! assert_eq!(blob.len(), 64);
|
|
||||||
//! # Ok(())
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Example (Positional)
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use rusqlite::blob::ZeroBlob;
|
|
||||||
//! # use rusqlite::{Connection, MAIN_DB};
|
|
||||||
//! # use std::error::Error;
|
|
||||||
//! # fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
//! let db = Connection::open_in_memory()?;
|
|
||||||
//! db.execute_batch("CREATE TABLE test_table (content BLOB);")?;
|
|
||||||
//! // Insert a blob into the `content` column of `test_table`. Note that the Blob
|
|
||||||
//! // I/O API provides no way of inserting or resizing blobs in the DB -- this
|
|
||||||
//! // must be done via SQL.
|
|
||||||
//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
|
|
||||||
//! // Get the row id off the blob we just inserted.
|
|
||||||
//! let rowid = db.last_insert_rowid();
|
|
||||||
//! // Open the blob we just inserted for IO.
|
|
||||||
//! let mut blob = db.blob_open(MAIN_DB, "test_table", "content", rowid, false)?;
|
|
||||||
//! // Write some data into the blob.
|
|
||||||
//! blob.write_at(b"ABCDEF", 2)?;
|
|
||||||
//!
|
|
||||||
//! // Read the whole blob into a local buffer.
|
|
||||||
//! let mut buf = [0u8; 10];
|
|
||||||
//! blob.read_at_exact(&mut buf, 0)?;
|
|
||||||
//! assert_eq!(&buf, b"\0\0ABCDEF\0\0");
|
|
||||||
//!
|
|
||||||
//! // Insert another blob, this time using a parameter passed in from
|
|
||||||
//! // rust (potentially with a dynamic size).
|
|
||||||
//! db.execute(
|
|
||||||
//! "INSERT INTO test_table (content) VALUES (?1)",
|
|
||||||
//! [ZeroBlob(64)],
|
|
||||||
//! )?;
|
|
||||||
//!
|
|
||||||
//! // given a new row ID, we can reopen the blob on that row
|
|
||||||
//! let rowid = db.last_insert_rowid();
|
|
||||||
//! blob.reopen(rowid)?;
|
|
||||||
//! assert_eq!(blob.len(), 64);
|
|
||||||
//! # Ok(())
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
use std::cmp::min;
|
|
||||||
use std::io;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use super::ffi;
|
|
||||||
use super::types::{ToSql, ToSqlOutput};
|
|
||||||
use crate::{Connection, Name, Result};
|
|
||||||
|
|
||||||
mod pos_io;
|
|
||||||
|
|
||||||
/// Handle to an open BLOB. See
|
|
||||||
/// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion.
|
|
||||||
pub struct Blob<'conn> {
|
|
||||||
conn: &'conn Connection,
|
|
||||||
blob: *mut ffi::sqlite3_blob,
|
|
||||||
// used by std::io implementations,
|
|
||||||
pos: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Open a handle to the BLOB located in `row_id`,
|
|
||||||
/// `column`, `table` in database `db`.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a
|
|
||||||
/// C-compatible string or if the underlying SQLite BLOB open call
|
|
||||||
/// fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn blob_open<D: Name, N: Name>(
|
|
||||||
&self,
|
|
||||||
db: D,
|
|
||||||
table: N,
|
|
||||||
column: N,
|
|
||||||
row_id: i64,
|
|
||||||
read_only: bool,
|
|
||||||
) -> Result<Blob<'_>> {
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
let mut blob = ptr::null_mut();
|
|
||||||
let db = db.as_cstr()?;
|
|
||||||
let table = table.as_cstr()?;
|
|
||||||
let column = column.as_cstr()?;
|
|
||||||
let rc = unsafe {
|
|
||||||
ffi::sqlite3_blob_open(
|
|
||||||
c.db(),
|
|
||||||
db.as_ptr(),
|
|
||||||
table.as_ptr(),
|
|
||||||
column.as_ptr(),
|
|
||||||
row_id,
|
|
||||||
!read_only as std::ffi::c_int,
|
|
||||||
&mut blob,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
c.decode_result(rc).map(|_| Blob {
|
|
||||||
conn: self,
|
|
||||||
blob,
|
|
||||||
pos: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Blob<'_> {
|
|
||||||
/// Move a BLOB handle to a new row.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite BLOB reopen call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn reopen(&mut self, row: i64) -> Result<()> {
|
|
||||||
let rc = unsafe { ffi::sqlite3_blob_reopen(self.blob, row) };
|
|
||||||
if rc != ffi::SQLITE_OK {
|
|
||||||
return self.conn.decode_result(rc);
|
|
||||||
}
|
|
||||||
self.pos = 0;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the size in bytes of the BLOB.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn size(&self) -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_blob_bytes(self.blob) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the current size in bytes of the BLOB.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.size().try_into().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return true if the BLOB is empty.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.size() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Close a BLOB handle.
|
|
||||||
///
|
|
||||||
/// Calling `close` explicitly is not required (the BLOB will be closed
|
|
||||||
/// when the `Blob` is dropped), but it is available, so you can get any
|
|
||||||
/// errors that occur.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite close call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn close(mut self) -> Result<()> {
|
|
||||||
self.close_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn close_(&mut self) -> Result<()> {
|
|
||||||
let rc = unsafe { ffi::sqlite3_blob_close(self.blob) };
|
|
||||||
self.blob = ptr::null_mut();
|
|
||||||
self.conn.decode_result(rc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Read for Blob<'_> {
|
|
||||||
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of
|
|
||||||
/// the blob has been reached.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite read call fails.
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
let max_allowed_len = (self.size() - self.pos) as usize;
|
|
||||||
let n = min(buf.len(), max_allowed_len) as i32;
|
|
||||||
if n <= 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr().cast(), n, self.pos) };
|
|
||||||
self.conn
|
|
||||||
.decode_result(rc)
|
|
||||||
.map(|_| {
|
|
||||||
self.pos += n;
|
|
||||||
n as usize
|
|
||||||
})
|
|
||||||
.map_err(io::Error::other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Write for Blob<'_> {
|
|
||||||
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of
|
|
||||||
/// the blob has been reached; consider using `Write::write_all(buf)`
|
|
||||||
/// if you want to get an error if the entirety of the buffer cannot be
|
|
||||||
/// written.
|
|
||||||
///
|
|
||||||
/// This function may only modify the contents of the BLOB; it is not
|
|
||||||
/// possible to increase the size of a BLOB using this API.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite write call fails.
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
let max_allowed_len = (self.size() - self.pos) as usize;
|
|
||||||
let n = min(buf.len(), max_allowed_len) as i32;
|
|
||||||
if n <= 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
|
|
||||||
self.conn
|
|
||||||
.decode_result(rc)
|
|
||||||
.map(|_| {
|
|
||||||
self.pos += n;
|
|
||||||
n as usize
|
|
||||||
})
|
|
||||||
.map_err(io::Error::other)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Seek for Blob<'_> {
|
|
||||||
/// Seek to an offset, in bytes, in BLOB.
|
|
||||||
#[inline]
|
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
|
||||||
let pos = match pos {
|
|
||||||
io::SeekFrom::Start(offset) => offset as i64,
|
|
||||||
io::SeekFrom::Current(offset) => i64::from(self.pos) + offset,
|
|
||||||
io::SeekFrom::End(offset) => i64::from(self.size()) + offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
if pos < 0 {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"invalid seek to negative position",
|
|
||||||
))
|
|
||||||
} else if pos > i64::from(self.size()) {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"invalid seek to position past end of blob",
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
self.pos = pos as i32;
|
|
||||||
Ok(pos as u64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(unused_must_use)]
|
|
||||||
impl Drop for Blob<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.close_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// BLOB of length N that is filled with zeroes.
|
|
||||||
///
|
|
||||||
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is
|
|
||||||
/// later written using incremental BLOB I/O routines.
|
|
||||||
///
|
|
||||||
/// A negative value for the zeroblob results in a zero-length BLOB.
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct ZeroBlob(pub i32);
|
|
||||||
|
|
||||||
impl ToSql for ZeroBlob {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let Self(length) = *self;
|
|
||||||
Ok(ToSqlOutput::ZeroBlob(length))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result, MAIN_DB};
|
|
||||||
use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
fn db_with_test_blob() -> Result<(Connection, i64)> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let sql = "BEGIN;
|
|
||||||
CREATE TABLE test (content BLOB);
|
|
||||||
INSERT INTO test VALUES (ZEROBLOB(10));
|
|
||||||
END;";
|
|
||||||
db.execute_batch(sql)?;
|
|
||||||
let rowid = db.last_insert_rowid();
|
|
||||||
Ok((db, rowid))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_blob() -> Result<()> {
|
|
||||||
let (db, rowid) = db_with_test_blob()?;
|
|
||||||
|
|
||||||
let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?;
|
|
||||||
assert!(!blob.is_empty());
|
|
||||||
assert_eq!(10, blob.len());
|
|
||||||
assert_eq!(4, blob.write(b"Clob").unwrap());
|
|
||||||
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
|
|
||||||
assert_eq!(0, blob.write(b"5678").unwrap()); // still cannot write past 10
|
|
||||||
blob.flush().unwrap();
|
|
||||||
|
|
||||||
blob.reopen(rowid)?;
|
|
||||||
blob.close()?;
|
|
||||||
|
|
||||||
blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, true)?;
|
|
||||||
let mut bytes = [0u8; 5];
|
|
||||||
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(&bytes, b"Clob5");
|
|
||||||
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(&bytes, b"67890");
|
|
||||||
assert_eq!(0, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
|
|
||||||
blob.seek(SeekFrom::Start(2)).unwrap();
|
|
||||||
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(&bytes, b"ob567");
|
|
||||||
|
|
||||||
// only first 4 bytes of `bytes` should be read into
|
|
||||||
blob.seek(SeekFrom::Current(-1)).unwrap();
|
|
||||||
assert_eq!(4, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(&bytes, b"78907");
|
|
||||||
|
|
||||||
blob.seek(SeekFrom::End(-6)).unwrap();
|
|
||||||
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(&bytes, b"56789");
|
|
||||||
|
|
||||||
blob.reopen(rowid)?;
|
|
||||||
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(&bytes, b"Clob5");
|
|
||||||
|
|
||||||
// should not be able to seek negative or past end
|
|
||||||
blob.seek(SeekFrom::Current(-20)).unwrap_err();
|
|
||||||
blob.seek(SeekFrom::End(0)).unwrap();
|
|
||||||
blob.seek(SeekFrom::Current(1)).unwrap_err();
|
|
||||||
|
|
||||||
// write_all should detect when we return Ok(0) because there is no space left,
|
|
||||||
// and return a write error
|
|
||||||
blob.reopen(rowid)?;
|
|
||||||
blob.write_all(b"0123456789x").unwrap_err();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_blob_in_bufreader() -> Result<()> {
|
|
||||||
let (db, rowid) = db_with_test_blob()?;
|
|
||||||
|
|
||||||
let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?;
|
|
||||||
assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
|
|
||||||
|
|
||||||
blob.reopen(rowid)?;
|
|
||||||
let mut reader = BufReader::new(blob);
|
|
||||||
|
|
||||||
let mut line = String::new();
|
|
||||||
assert_eq!(4, reader.read_line(&mut line).unwrap());
|
|
||||||
assert_eq!("one\n", line);
|
|
||||||
|
|
||||||
line.truncate(0);
|
|
||||||
assert_eq!(4, reader.read_line(&mut line).unwrap());
|
|
||||||
assert_eq!("two\n", line);
|
|
||||||
|
|
||||||
line.truncate(0);
|
|
||||||
assert_eq!(2, reader.read_line(&mut line).unwrap());
|
|
||||||
assert_eq!("\0\0", line);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_blob_in_bufwriter() -> Result<()> {
|
|
||||||
let (db, rowid) = db_with_test_blob()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?;
|
|
||||||
let mut writer = BufWriter::new(blob);
|
|
||||||
|
|
||||||
// trying to write too much and then flush should fail
|
|
||||||
assert_eq!(8, writer.write(b"01234567").unwrap());
|
|
||||||
assert_eq!(8, writer.write(b"01234567").unwrap());
|
|
||||||
writer.flush().unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// ... but it should've written the first 10 bytes
|
|
||||||
let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?;
|
|
||||||
let mut bytes = [0u8; 10];
|
|
||||||
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(b"0123456701", &bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?;
|
|
||||||
let mut writer = BufWriter::new(blob);
|
|
||||||
|
|
||||||
// trying to write_all too much should fail
|
|
||||||
writer.write_all(b"aaaaaaaaaabbbbb").unwrap();
|
|
||||||
writer.flush().unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// ... but it should've written the first 10 bytes
|
|
||||||
let mut blob = db.blob_open(MAIN_DB, c"test", c"content", rowid, false)?;
|
|
||||||
let mut bytes = [0u8; 10];
|
|
||||||
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
|
|
||||||
assert_eq!(b"aaaaaaaaaa", &bytes);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn zero_blob() -> Result<()> {
|
|
||||||
use crate::types::ToSql;
|
|
||||||
let zb = super::ZeroBlob(1);
|
|
||||||
assert!(zb.to_sql().is_ok());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
274
vendor/rusqlite/src/blob/pos_io.rs
vendored
274
vendor/rusqlite/src/blob/pos_io.rs
vendored
@@ -1,274 +0,0 @@
|
|||||||
use super::Blob;
|
|
||||||
|
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
use std::slice::from_raw_parts_mut;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::{Error, Result};
|
|
||||||
|
|
||||||
impl Blob<'_> {
|
|
||||||
/// Write `buf` to `self` starting at `write_start`, returning an error if
|
|
||||||
/// `write_start + buf.len()` is past the end of the blob.
|
|
||||||
///
|
|
||||||
/// If an error is returned, no data is written.
|
|
||||||
///
|
|
||||||
/// Note: the blob cannot be resized using this function -- that must be
|
|
||||||
/// done using SQL (for example, an `UPDATE` statement).
|
|
||||||
///
|
|
||||||
/// Note: This is part of the positional I/O API, and thus takes an absolute
|
|
||||||
/// position write to, instead of using the internal position that can be
|
|
||||||
/// manipulated by the `std::io` traits.
|
|
||||||
///
|
|
||||||
/// Unlike the similarly named [`FileExt::write_at`][fext_write_at] function
|
|
||||||
/// (from `std::os::unix`), it's always an error to perform a "short write".
|
|
||||||
///
|
|
||||||
/// [fext_write_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at
|
|
||||||
#[inline]
|
|
||||||
pub fn write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
|
|
||||||
let len = self.len();
|
|
||||||
|
|
||||||
if buf.len().saturating_add(write_start) > len {
|
|
||||||
return Err(Error::BlobSizeError);
|
|
||||||
}
|
|
||||||
// We know `len` fits in an `i32`, so either:
|
|
||||||
//
|
|
||||||
// 1. `buf.len() + write_start` overflows, in which case we'd hit the
|
|
||||||
// return above (courtesy of `saturating_add`).
|
|
||||||
//
|
|
||||||
// 2. `buf.len() + write_start` doesn't overflow but is larger than len,
|
|
||||||
// in which case ditto.
|
|
||||||
//
|
|
||||||
// 3. `buf.len() + write_start` doesn't overflow but is less than len.
|
|
||||||
// This means that both `buf.len()` and `write_start` can also be
|
|
||||||
// losslessly converted to i32, since `len` came from an i32.
|
|
||||||
// Sanity check the above.
|
|
||||||
debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
|
|
||||||
self.conn.decode_result(unsafe {
|
|
||||||
ffi::sqlite3_blob_write(
|
|
||||||
self.blob,
|
|
||||||
buf.as_ptr().cast(),
|
|
||||||
buf.len() as i32,
|
|
||||||
write_start as i32,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An alias for `write_at` provided for compatibility with the conceptually
|
|
||||||
/// equivalent [`std::os::unix::FileExt::write_all_at`][write_all_at]
|
|
||||||
/// function from libstd:
|
|
||||||
///
|
|
||||||
/// [write_all_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#method.write_all_at
|
|
||||||
#[inline]
|
|
||||||
pub fn write_all_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
|
|
||||||
self.write_at(buf, write_start)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read as much as possible from `offset` to `offset + buf.len()` out of
|
|
||||||
/// `self`, writing into `buf`. On success, returns the number of bytes
|
|
||||||
/// written.
|
|
||||||
///
|
|
||||||
/// If there's insufficient data in `self`, then the returned value will be
|
|
||||||
/// less than `buf.len()`.
|
|
||||||
///
|
|
||||||
/// See also [`Blob::raw_read_at`], which can take an uninitialized buffer,
|
|
||||||
/// or [`Blob::read_at_exact`] which returns an error if the entire `buf` is
|
|
||||||
/// not read.
|
|
||||||
///
|
|
||||||
/// Note: This is part of the positional I/O API, and thus takes an absolute
|
|
||||||
/// position to read from, instead of using the internal position that can
|
|
||||||
/// be manipulated by the `std::io` traits. Consequently, it does not change
|
|
||||||
/// that value either.
|
|
||||||
#[inline]
|
|
||||||
pub fn read_at(&self, buf: &mut [u8], read_start: usize) -> Result<usize> {
|
|
||||||
// Safety: this is safe because `raw_read_at` never stores uninitialized
|
|
||||||
// data into `as_uninit`.
|
|
||||||
let as_uninit: &mut [MaybeUninit<u8>] =
|
|
||||||
unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
|
|
||||||
self.raw_read_at(as_uninit, read_start).map(|s| s.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read as much as possible from `offset` to `offset + buf.len()` out of
|
|
||||||
/// `self`, writing into `buf`. On success, returns the portion of `buf`
|
|
||||||
/// which was initialized by this call.
|
|
||||||
///
|
|
||||||
/// If there's insufficient data in `self`, then the returned value will be
|
|
||||||
/// shorter than `buf`.
|
|
||||||
///
|
|
||||||
/// See also [`Blob::read_at`], which takes a `&mut [u8]` buffer instead of
|
|
||||||
/// a slice of `MaybeUninit<u8>`.
|
|
||||||
///
|
|
||||||
/// Note: This is part of the positional I/O API, and thus takes an absolute
|
|
||||||
/// position to read from, instead of using the internal position that can
|
|
||||||
/// be manipulated by the `std::io` traits. Consequently, it does not change
|
|
||||||
/// that value either.
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_read_at<'a>(
|
|
||||||
&self,
|
|
||||||
buf: &'a mut [MaybeUninit<u8>],
|
|
||||||
read_start: usize,
|
|
||||||
) -> Result<&'a mut [u8]> {
|
|
||||||
let len = self.len();
|
|
||||||
|
|
||||||
let read_len = match len.checked_sub(read_start) {
|
|
||||||
None | Some(0) => 0,
|
|
||||||
Some(v) => v.min(buf.len()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if read_len == 0 {
|
|
||||||
// We could return `Ok(&mut [])`, but it seems confusing that the
|
|
||||||
// pointers don't match, so fabricate an empty slice of u8 with the
|
|
||||||
// same base pointer as `buf`.
|
|
||||||
let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), 0) };
|
|
||||||
return Ok(empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point we believe `read_start as i32` is lossless because:
|
|
||||||
//
|
|
||||||
// 1. `len as i32` is known to be lossless, since it comes from a SQLite
|
|
||||||
// api returning an i32.
|
|
||||||
//
|
|
||||||
// 2. If we got here, `len.checked_sub(read_start)` was Some (or else
|
|
||||||
// we'd have hit the `if read_len == 0` early return), so `len` must
|
|
||||||
// be larger than `read_start`, and so it must fit in i32 as well.
|
|
||||||
debug_assert!(i32::try_from(read_start).is_ok());
|
|
||||||
|
|
||||||
// We also believe that `read_start + read_len <= len` because:
|
|
||||||
//
|
|
||||||
// 1. This is equivalent to `read_len <= len - read_start` via algebra.
|
|
||||||
// 2. We know that `read_len` is `min(len - read_start, buf.len())`
|
|
||||||
// 3. Expanding, this is `min(len - read_start, buf.len()) <= len - read_start`,
|
|
||||||
// or `min(A, B) <= A` which is clearly true.
|
|
||||||
//
|
|
||||||
// Note that this stuff is in debug_assert so no need to use checked_add
|
|
||||||
// and such -- we'll always panic on overflow in debug builds.
|
|
||||||
debug_assert!(read_start + read_len <= len);
|
|
||||||
|
|
||||||
// These follow naturally.
|
|
||||||
debug_assert!(buf.len() >= read_len);
|
|
||||||
debug_assert!(i32::try_from(buf.len()).is_ok());
|
|
||||||
debug_assert!(i32::try_from(read_len).is_ok());
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
self.conn.decode_result(ffi::sqlite3_blob_read(
|
|
||||||
self.blob,
|
|
||||||
buf.as_mut_ptr().cast(),
|
|
||||||
read_len as i32,
|
|
||||||
read_start as i32,
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), read_len))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Equivalent to [`Blob::read_at`], but returns a `BlobSizeError` if `buf`
|
|
||||||
/// is not fully initialized.
|
|
||||||
#[inline]
|
|
||||||
pub fn read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()> {
|
|
||||||
let n = self.read_at(buf, read_start)?;
|
|
||||||
if n != buf.len() {
|
|
||||||
Err(Error::BlobSizeError)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Equivalent to [`Blob::raw_read_at`], but returns a `BlobSizeError` if
|
|
||||||
/// `buf` is not fully initialized.
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_read_at_exact<'a>(
|
|
||||||
&self,
|
|
||||||
buf: &'a mut [MaybeUninit<u8>],
|
|
||||||
read_start: usize,
|
|
||||||
) -> Result<&'a mut [u8]> {
|
|
||||||
let buflen = buf.len();
|
|
||||||
let initted = self.raw_read_at(buf, read_start)?;
|
|
||||||
if initted.len() != buflen {
|
|
||||||
Err(Error::BlobSizeError)
|
|
||||||
} else {
|
|
||||||
Ok(initted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result, MAIN_DB};
|
|
||||||
// to ensure we don't modify seek pos
|
|
||||||
use std::io::Seek as _;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pos_io() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE test_table(content BLOB);")?;
|
|
||||||
db.execute("INSERT INTO test_table(content) VALUES (ZEROBLOB(10))", [])?;
|
|
||||||
|
|
||||||
let rowid = db.last_insert_rowid();
|
|
||||||
let mut blob = db.blob_open(MAIN_DB, c"test_table", c"content", rowid, false)?;
|
|
||||||
// modify the seek pos to ensure we aren't using it or modifying it.
|
|
||||||
blob.seek(std::io::SeekFrom::Start(1)).unwrap();
|
|
||||||
|
|
||||||
let one2ten: [u8; 10] = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
||||||
blob.write_at(&one2ten, 0)?;
|
|
||||||
|
|
||||||
let mut s = [0u8; 10];
|
|
||||||
blob.read_at_exact(&mut s, 0)?;
|
|
||||||
assert_eq!(&s, &one2ten, "write should go through");
|
|
||||||
blob.read_at_exact(&mut s, 1).unwrap_err();
|
|
||||||
|
|
||||||
blob.read_at_exact(&mut s, 0)?;
|
|
||||||
assert_eq!(&s, &one2ten, "should be unchanged");
|
|
||||||
|
|
||||||
let mut fives = [0u8; 5];
|
|
||||||
blob.read_at_exact(&mut fives, 0)?;
|
|
||||||
assert_eq!(&fives, &[1u8, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
blob.read_at_exact(&mut fives, 5)?;
|
|
||||||
assert_eq!(&fives, &[6u8, 7, 8, 9, 10]);
|
|
||||||
blob.read_at_exact(&mut fives, 7).unwrap_err();
|
|
||||||
blob.read_at_exact(&mut fives, 12).unwrap_err();
|
|
||||||
blob.read_at_exact(&mut fives, 10).unwrap_err();
|
|
||||||
blob.read_at_exact(&mut fives, i32::MAX as usize)
|
|
||||||
.unwrap_err();
|
|
||||||
blob.read_at_exact(&mut fives, i32::MAX as usize + 1)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
// zero length writes are fine if in bounds
|
|
||||||
blob.read_at_exact(&mut [], 10)?;
|
|
||||||
blob.read_at_exact(&mut [], 0)?;
|
|
||||||
blob.read_at_exact(&mut [], 5)?;
|
|
||||||
|
|
||||||
blob.write_all_at(&[16, 17, 18, 19, 20], 5)?;
|
|
||||||
blob.read_at_exact(&mut s, 0)?;
|
|
||||||
assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
|
|
||||||
|
|
||||||
blob.write_at(&[100, 99, 98, 97, 96], 6).unwrap_err();
|
|
||||||
blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize)
|
|
||||||
.unwrap_err();
|
|
||||||
blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
blob.read_at_exact(&mut s, 0)?;
|
|
||||||
assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
|
|
||||||
|
|
||||||
let mut s2: [std::mem::MaybeUninit<u8>; 10] = [std::mem::MaybeUninit::uninit(); 10];
|
|
||||||
{
|
|
||||||
let read = blob.raw_read_at_exact(&mut s2, 0)?;
|
|
||||||
assert_eq!(read, &s);
|
|
||||||
assert!(std::ptr::eq(read.as_ptr(), s2.as_ptr().cast()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut empty = [];
|
|
||||||
assert!(std::ptr::eq(
|
|
||||||
blob.raw_read_at_exact(&mut empty, 0)?.as_ptr(),
|
|
||||||
empty.as_ptr().cast(),
|
|
||||||
));
|
|
||||||
blob.raw_read_at_exact(&mut s2, 5).unwrap_err();
|
|
||||||
|
|
||||||
let end_pos = blob.stream_position().unwrap();
|
|
||||||
assert_eq!(end_pos, 1);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
138
vendor/rusqlite/src/busy.rs
vendored
138
vendor/rusqlite/src/busy.rs
vendored
@@ -1,138 +0,0 @@
|
|||||||
//! Busy handler (when the database is locked)
|
|
||||||
use std::ffi::{c_int, c_void};
|
|
||||||
use std::mem;
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
use std::ptr;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::{Connection, InnerConnection, Result};
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Set a busy handler that sleeps for a specified amount of time when a
|
|
||||||
/// table is locked. The handler will sleep multiple times until at
|
|
||||||
/// least "ms" milliseconds of sleeping have accumulated.
|
|
||||||
///
|
|
||||||
/// Calling this routine with an argument equal to zero turns off all busy
|
|
||||||
/// handlers.
|
|
||||||
///
|
|
||||||
/// There can only be a single busy handler for a particular database
|
|
||||||
/// connection at any given moment. If another busy handler was defined
|
|
||||||
/// (using [`busy_handler`](Connection::busy_handler)) prior to calling this
|
|
||||||
/// routine, that other busy handler is cleared.
|
|
||||||
///
|
|
||||||
/// Newly created connections currently have a default busy timeout of
|
|
||||||
/// 5000ms, but this may be subject to change.
|
|
||||||
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
|
|
||||||
let ms: i32 = timeout
|
|
||||||
.as_secs()
|
|
||||||
.checked_mul(1000)
|
|
||||||
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
|
|
||||||
.and_then(|t| t.try_into().ok())
|
|
||||||
.expect("too big");
|
|
||||||
self.db.borrow_mut().busy_timeout(ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a callback to handle `SQLITE_BUSY` errors.
|
|
||||||
///
|
|
||||||
/// If the busy callback is `None`, then `SQLITE_BUSY` is returned
|
|
||||||
/// immediately upon encountering the lock. The argument to the busy
|
|
||||||
/// handler callback is the number of times that the
|
|
||||||
/// busy handler has been invoked previously for the
|
|
||||||
/// same locking event. If the busy callback returns `false`, then no
|
|
||||||
/// additional attempts are made to access the
|
|
||||||
/// database and `SQLITE_BUSY` is returned to the
|
|
||||||
/// application. If the callback returns `true`, then another attempt
|
|
||||||
/// is made to access the database and the cycle repeats.
|
|
||||||
///
|
|
||||||
/// There can only be a single busy handler defined for each database
|
|
||||||
/// connection. Setting a new busy handler clears any previously set
|
|
||||||
/// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout)
|
|
||||||
/// or evaluating `PRAGMA busy_timeout=N` will change the busy handler
|
|
||||||
/// and thus clear any previously set busy handler.
|
|
||||||
///
|
|
||||||
/// Newly created connections default to a
|
|
||||||
/// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout
|
|
||||||
/// of 5000ms, although this is subject to change.
|
|
||||||
pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
|
|
||||||
unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
|
|
||||||
let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
|
|
||||||
c_int::from(catch_unwind(|| handler_fn(count)).unwrap_or_default())
|
|
||||||
}
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
c.decode_result(unsafe {
|
|
||||||
ffi::sqlite3_busy_handler(
|
|
||||||
c.db(),
|
|
||||||
callback.as_ref().map(|_| busy_handler_callback as _),
|
|
||||||
callback.map_or_else(ptr::null_mut, |f| f as *mut c_void),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerConnection {
|
|
||||||
#[inline]
|
|
||||||
fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
|
|
||||||
let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
|
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, ErrorCode, Result, TransactionBehavior};
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
all(target_family = "wasm", target_os = "unknown"),
|
|
||||||
ignore = "no filesystem on this platform"
|
|
||||||
)]
|
|
||||||
#[test]
|
|
||||||
fn test_default_busy() -> Result<()> {
|
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
|
||||||
let path = temp_dir.path().join("test.db3");
|
|
||||||
|
|
||||||
let mut db1 = Connection::open(&path)?;
|
|
||||||
let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
|
|
||||||
let db2 = Connection::open(&path)?;
|
|
||||||
let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
|
|
||||||
assert_eq!(
|
|
||||||
r.unwrap_err().sqlite_error_code(),
|
|
||||||
Some(ErrorCode::DatabaseBusy)
|
|
||||||
);
|
|
||||||
tx1.rollback()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
all(target_family = "wasm", target_os = "unknown"),
|
|
||||||
ignore = "no filesystem on this platform"
|
|
||||||
)]
|
|
||||||
#[test]
|
|
||||||
fn test_busy_handler() -> Result<()> {
|
|
||||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
|
||||||
fn busy_handler(n: i32) -> bool {
|
|
||||||
if n > 2 {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
CALLED.swap(true, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
|
||||||
let path = temp_dir.path().join("busy-handler.db3");
|
|
||||||
|
|
||||||
let db1 = Connection::open(&path)?;
|
|
||||||
db1.execute_batch("CREATE TABLE IF NOT EXISTS t(a)")?;
|
|
||||||
let db2 = Connection::open(&path)?;
|
|
||||||
db2.busy_handler(Some(busy_handler))?;
|
|
||||||
db1.execute_batch("BEGIN EXCLUSIVE")?;
|
|
||||||
let err = db2.prepare("SELECT * FROM t").unwrap_err();
|
|
||||||
assert_eq!(err.sqlite_error_code(), Some(ErrorCode::DatabaseBusy));
|
|
||||||
assert!(CALLED.load(Ordering::Relaxed));
|
|
||||||
db1.busy_handler(None)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
351
vendor/rusqlite/src/cache.rs
vendored
351
vendor/rusqlite/src/cache.rs
vendored
@@ -1,351 +0,0 @@
|
|||||||
//! Prepared statements cache for faster execution.
|
|
||||||
|
|
||||||
use crate::raw_statement::RawStatement;
|
|
||||||
use crate::{Connection, PrepFlags, Result, Statement};
|
|
||||||
use hashlink::LruCache;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Prepare a SQL statement for execution, returning a previously prepared
|
|
||||||
/// (but not currently in-use) statement if one is available. The
|
|
||||||
/// returned statement will be cached for reuse by future calls to
|
|
||||||
/// [`prepare_cached`](Connection::prepare_cached) once it is dropped.
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// fn insert_new_people(conn: &Connection) -> Result<()> {
|
|
||||||
/// {
|
|
||||||
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
|
|
||||||
/// stmt.execute(["Joe Smith"])?;
|
|
||||||
/// }
|
|
||||||
/// {
|
|
||||||
/// // This will return the same underlying SQLite statement handle without
|
|
||||||
/// // having to prepare it again.
|
|
||||||
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
|
|
||||||
/// stmt.execute(["Bob Jones"])?;
|
|
||||||
/// }
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
|
||||||
/// or if the underlying SQLite call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
|
|
||||||
self.cache.get(self, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum number of cached prepared statements this connection
|
|
||||||
/// will hold. By default, a connection will hold a relatively small
|
|
||||||
/// number of cached statements. If you need more, or know that you
|
|
||||||
/// will not use cached statements, you
|
|
||||||
/// can set the capacity manually using this method.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
|
|
||||||
self.cache.set_capacity(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove/finalize all prepared statements currently in the cache.
|
|
||||||
#[inline]
|
|
||||||
pub fn flush_prepared_statement_cache(&self) {
|
|
||||||
self.cache.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepared statements LRU cache.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
|
|
||||||
|
|
||||||
unsafe impl Send for StatementCache {}
|
|
||||||
|
|
||||||
/// Cacheable statement.
|
|
||||||
///
|
|
||||||
/// Statement will return automatically to the cache by default.
|
|
||||||
/// If you want the statement to be discarded, call
|
|
||||||
/// [`discard()`](CachedStatement::discard) on it.
|
|
||||||
pub struct CachedStatement<'conn> {
|
|
||||||
stmt: Option<Statement<'conn>>,
|
|
||||||
cache: &'conn StatementCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'conn> Deref for CachedStatement<'conn> {
|
|
||||||
type Target = Statement<'conn>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Statement<'conn> {
|
|
||||||
self.stmt.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'conn> DerefMut for CachedStatement<'conn> {
|
|
||||||
#[inline]
|
|
||||||
fn deref_mut(&mut self) -> &mut Statement<'conn> {
|
|
||||||
self.stmt.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for CachedStatement<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(stmt) = self.stmt.take() {
|
|
||||||
self.cache.cache_stmt(unsafe { stmt.into_raw() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CachedStatement<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
|
|
||||||
CachedStatement {
|
|
||||||
stmt: Some(stmt),
|
|
||||||
cache,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Discard the statement, preventing it from being returned to its
|
|
||||||
/// [`Connection`]'s collection of cached statements.
|
|
||||||
#[inline]
|
|
||||||
pub fn discard(mut self) {
|
|
||||||
self.stmt = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatementCache {
|
|
||||||
/// Create a statement cache.
|
|
||||||
#[inline]
|
|
||||||
pub fn with_capacity(capacity: usize) -> Self {
|
|
||||||
Self(RefCell::new(LruCache::new(capacity)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_capacity(&self, capacity: usize) {
|
|
||||||
self.0.borrow_mut().set_capacity(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search the cache for a prepared-statement object that implements `sql`.
|
|
||||||
// If no such prepared-statement can be found, allocate and prepare a new one.
|
|
||||||
//
|
|
||||||
// # Failure
|
|
||||||
//
|
|
||||||
// Will return `Err` if no cached statement can be found and the underlying
|
|
||||||
// SQLite prepare call fails.
|
|
||||||
fn get<'conn>(
|
|
||||||
&'conn self,
|
|
||||||
conn: &'conn Connection,
|
|
||||||
sql: &str,
|
|
||||||
) -> Result<CachedStatement<'conn>> {
|
|
||||||
let trimmed = sql.trim();
|
|
||||||
let mut cache = self.0.borrow_mut();
|
|
||||||
let stmt = match cache.remove(trimmed) {
|
|
||||||
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
|
|
||||||
None => conn.prepare_with_flags(trimmed, PrepFlags::SQLITE_PREPARE_PERSISTENT),
|
|
||||||
};
|
|
||||||
stmt.map(|mut stmt| {
|
|
||||||
stmt.stmt.set_statement_cache_key(trimmed);
|
|
||||||
CachedStatement::new(stmt, self)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a statement to the cache.
|
|
||||||
fn cache_stmt(&self, mut stmt: RawStatement) {
|
|
||||||
if stmt.is_null() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut cache = self.0.borrow_mut();
|
|
||||||
stmt.clear_bindings();
|
|
||||||
if let Some(sql) = stmt.statement_cache_key() {
|
|
||||||
cache.insert(sql, stmt);
|
|
||||||
} else {
|
|
||||||
debug_assert!(
|
|
||||||
false,
|
|
||||||
"bug in statement cache code, statement returned to cache that without key"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flush(&self) {
|
|
||||||
let mut cache = self.0.borrow_mut();
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::StatementCache;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
|
|
||||||
impl StatementCache {
|
|
||||||
fn clear(&self) {
|
|
||||||
self.0.borrow_mut().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.0.borrow().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn capacity(&self) -> usize {
|
|
||||||
self.0.borrow().capacity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cache() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let cache = &db.cache;
|
|
||||||
let initial_capacity = cache.capacity();
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert!(initial_capacity > 0);
|
|
||||||
|
|
||||||
let sql = "PRAGMA schema_version";
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(1, cache.len());
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(1, cache.len());
|
|
||||||
|
|
||||||
cache.clear();
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(initial_capacity, cache.capacity());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_set_capacity() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let cache = &db.cache;
|
|
||||||
|
|
||||||
let sql = "PRAGMA schema_version";
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(1, cache.len());
|
|
||||||
|
|
||||||
db.set_prepared_statement_cache_capacity(0);
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
|
|
||||||
db.set_prepared_statement_cache_capacity(8);
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(1, cache.len());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_discard() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let cache = &db.cache;
|
|
||||||
|
|
||||||
let sql = "PRAGMA schema_version";
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
stmt.discard();
|
|
||||||
}
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ddl() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch(
|
|
||||||
r"
|
|
||||||
CREATE TABLE foo (x INT);
|
|
||||||
INSERT INTO foo VALUES (1);
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let sql = "SELECT * FROM foo";
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next());
|
|
||||||
}
|
|
||||||
|
|
||||||
db.execute_batch(
|
|
||||||
r"
|
|
||||||
ALTER TABLE foo ADD COLUMN y INT;
|
|
||||||
UPDATE foo SET y = 2;
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(
|
|
||||||
Ok(Some((1i32, 2i32))),
|
|
||||||
stmt.query([])?.map(|r| Ok((r.get(0)?, r.get(1)?))).next()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_connection_close() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
conn.prepare_cached("SELECT * FROM sqlite_master;")?;
|
|
||||||
|
|
||||||
conn.close().expect("connection not closed");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cache_key() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let cache = &db.cache;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
|
|
||||||
//let sql = " PRAGMA schema_version; -- comment";
|
|
||||||
let sql = "PRAGMA schema_version; ";
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(1, cache.len());
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare_cached(sql)?;
|
|
||||||
assert_eq!(0, cache.len());
|
|
||||||
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
|
|
||||||
}
|
|
||||||
assert_eq!(1, cache.len());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_stmt() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
conn.prepare_cached("")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
236
vendor/rusqlite/src/collation.rs
vendored
236
vendor/rusqlite/src/collation.rs
vendored
@@ -1,236 +0,0 @@
|
|||||||
//! Add, remove, or modify a collation
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::ffi::{c_char, c_int, c_void, CStr};
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
use std::ptr;
|
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::util::free_boxed_value;
|
|
||||||
use crate::{Connection, InnerConnection, Name, Result};
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Add or modify a collation.
|
|
||||||
#[inline]
|
|
||||||
pub fn create_collation<C, N: Name>(&self, collation_name: N, x_compare: C) -> Result<()>
|
|
||||||
where
|
|
||||||
C: Fn(&str, &str) -> Ordering + Send + 'static,
|
|
||||||
{
|
|
||||||
self.db
|
|
||||||
.borrow_mut()
|
|
||||||
.create_collation(collation_name, x_compare)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collation needed callback
|
|
||||||
#[inline]
|
|
||||||
pub fn collation_needed(&self, x_coll_needed: fn(&Self, &str) -> Result<()>) -> Result<()> {
|
|
||||||
self.db.borrow_mut().collation_needed(x_coll_needed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove collation.
|
|
||||||
#[inline]
|
|
||||||
pub fn remove_collation<N: Name>(&self, collation_name: N) -> Result<()> {
|
|
||||||
self.db.borrow_mut().remove_collation(collation_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerConnection {
|
|
||||||
/// ```compile_fail
|
|
||||||
/// use rusqlite::{Connection, Result};
|
|
||||||
/// fn main() -> Result<()> {
|
|
||||||
/// let db = Connection::open_in_memory()?;
|
|
||||||
/// {
|
|
||||||
/// let mut called = std::sync::atomic::AtomicBool::new(false);
|
|
||||||
/// db.create_collation("foo", |_, _| {
|
|
||||||
/// called.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
/// std::cmp::Ordering::Equal
|
|
||||||
/// })?;
|
|
||||||
/// }
|
|
||||||
/// let value: String = db.query_row(
|
|
||||||
/// "WITH cte(bar) AS
|
|
||||||
/// (VALUES ('v1'),('v2'),('v3'),('v4'),('v5'))
|
|
||||||
/// SELECT DISTINCT bar COLLATE foo FROM cte;",
|
|
||||||
/// [],
|
|
||||||
/// |row| row.get(0),
|
|
||||||
/// )?;
|
|
||||||
/// assert_eq!(value, "v1");
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn create_collation<C, N: Name>(&mut self, collation_name: N, x_compare: C) -> Result<()>
|
|
||||||
where
|
|
||||||
C: Fn(&str, &str) -> Ordering + Send + 'static,
|
|
||||||
{
|
|
||||||
unsafe extern "C" fn call_boxed_closure<C>(
|
|
||||||
arg1: *mut c_void,
|
|
||||||
arg2: c_int,
|
|
||||||
arg3: *const c_void,
|
|
||||||
arg4: c_int,
|
|
||||||
arg5: *const c_void,
|
|
||||||
) -> c_int
|
|
||||||
where
|
|
||||||
C: Fn(&str, &str) -> Ordering,
|
|
||||||
{
|
|
||||||
let r = catch_unwind(|| {
|
|
||||||
let boxed_f: *mut C = arg1.cast::<C>();
|
|
||||||
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
|
||||||
let s1 = {
|
|
||||||
let c_slice = slice::from_raw_parts(arg3.cast::<u8>(), arg2 as usize);
|
|
||||||
String::from_utf8_lossy(c_slice)
|
|
||||||
};
|
|
||||||
let s2 = {
|
|
||||||
let c_slice = slice::from_raw_parts(arg5.cast::<u8>(), arg4 as usize);
|
|
||||||
String::from_utf8_lossy(c_slice)
|
|
||||||
};
|
|
||||||
(*boxed_f)(s1.as_ref(), s2.as_ref())
|
|
||||||
});
|
|
||||||
let t = match r {
|
|
||||||
Err(_) => {
|
|
||||||
return -1; // FIXME How ?
|
|
||||||
}
|
|
||||||
Ok(r) => r,
|
|
||||||
};
|
|
||||||
|
|
||||||
match t {
|
|
||||||
Ordering::Less => -1,
|
|
||||||
Ordering::Equal => 0,
|
|
||||||
Ordering::Greater => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let boxed_f: *mut C = Box::into_raw(Box::new(x_compare));
|
|
||||||
let c_name = collation_name.as_cstr()?;
|
|
||||||
let flags = ffi::SQLITE_UTF8;
|
|
||||||
let r = unsafe {
|
|
||||||
ffi::sqlite3_create_collation_v2(
|
|
||||||
self.db(),
|
|
||||||
c_name.as_ptr(),
|
|
||||||
flags,
|
|
||||||
boxed_f.cast::<c_void>(),
|
|
||||||
Some(call_boxed_closure::<C>),
|
|
||||||
Some(free_boxed_value::<C>),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let res = self.decode_result(r);
|
|
||||||
// The xDestroy callback is not called if the sqlite3_create_collation_v2()
|
|
||||||
// function fails.
|
|
||||||
if res.is_err() {
|
|
||||||
drop(unsafe { Box::from_raw(boxed_f) });
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collation_needed(
|
|
||||||
&mut self,
|
|
||||||
x_coll_needed: fn(&Connection, &str) -> Result<()>,
|
|
||||||
) -> Result<()> {
|
|
||||||
use std::mem;
|
|
||||||
#[expect(clippy::needless_return)]
|
|
||||||
unsafe extern "C" fn collation_needed_callback(
|
|
||||||
arg1: *mut c_void,
|
|
||||||
arg2: *mut ffi::sqlite3,
|
|
||||||
e_text_rep: c_int,
|
|
||||||
arg3: *const c_char,
|
|
||||||
) {
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
if e_text_rep != ffi::SQLITE_UTF8 {
|
|
||||||
// TODO: validate
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let callback: fn(&Connection, &str) -> Result<()> = mem::transmute(arg1);
|
|
||||||
let res = catch_unwind(|| {
|
|
||||||
let conn = Connection::from_handle(arg2).unwrap();
|
|
||||||
let collation_name = CStr::from_ptr(arg3)
|
|
||||||
.to_str()
|
|
||||||
.expect("illegal collation sequence name");
|
|
||||||
callback(&conn, collation_name)
|
|
||||||
});
|
|
||||||
if res.is_err() {
|
|
||||||
return; // FIXME How ?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = unsafe {
|
|
||||||
ffi::sqlite3_collation_needed(
|
|
||||||
self.db(),
|
|
||||||
x_coll_needed as *mut c_void,
|
|
||||||
Some(collation_needed_callback),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn remove_collation<N: Name>(&mut self, collation_name: N) -> Result<()> {
|
|
||||||
let c_name = collation_name.as_cstr()?;
|
|
||||||
let r = unsafe {
|
|
||||||
ffi::sqlite3_create_collation_v2(
|
|
||||||
self.db(),
|
|
||||||
c_name.as_ptr(),
|
|
||||||
ffi::SQLITE_UTF8,
|
|
||||||
ptr::null_mut(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use unicase::UniCase;
|
|
||||||
|
|
||||||
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
|
|
||||||
UniCase::new(s1).cmp(&UniCase::new(s2))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unicase() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.create_collation(c"unicase", unicase_compare)?;
|
|
||||||
collate(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collate(db: Connection) -> Result<()> {
|
|
||||||
db.execute_batch(
|
|
||||||
"CREATE TABLE foo (bar);
|
|
||||||
INSERT INTO foo (bar) VALUES ('Maße');
|
|
||||||
INSERT INTO foo (bar) VALUES ('MASSE');",
|
|
||||||
)?;
|
|
||||||
let mut stmt = db.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")?;
|
|
||||||
let rows = stmt.query([])?;
|
|
||||||
assert_eq!(rows.count()?, 1);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
|
|
||||||
if "unicase" == collation_name {
|
|
||||||
db.create_collation(collation_name, unicase_compare)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_collation_needed() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.collation_needed(collation_needed)?;
|
|
||||||
collate(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn remove_collation() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.create_collation(c"unicase", unicase_compare)?;
|
|
||||||
db.remove_collation(c"unicase")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
576
vendor/rusqlite/src/column.rs
vendored
576
vendor/rusqlite/src/column.rs
vendored
@@ -1,576 +0,0 @@
|
|||||||
use std::ffi::{c_char, CStr};
|
|
||||||
use std::ptr;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::{Connection, Error, Name, Result, Statement};
|
|
||||||
|
|
||||||
/// Information about a column of a SQLite query.
|
|
||||||
#[cfg(feature = "column_decltype")]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Column<'stmt> {
|
|
||||||
name: &'stmt str,
|
|
||||||
decl_type: Option<&'stmt str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "column_decltype")]
|
|
||||||
impl Column<'_> {
|
|
||||||
/// Returns the name of the column.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the type of the column (`None` for expression).
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn decl_type(&self) -> Option<&str> {
|
|
||||||
self.decl_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metadata about the origin of a column of a SQLite query
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ColumnMetadata<'stmt> {
|
|
||||||
name: &'stmt str,
|
|
||||||
database_name: Option<&'stmt str>,
|
|
||||||
table_name: Option<&'stmt str>,
|
|
||||||
origin_name: Option<&'stmt str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
impl ColumnMetadata<'_> {
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
/// Returns the name of the column in the query results
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
/// Returns the database name from which the column originates
|
|
||||||
pub fn database_name(&self) -> Option<&str> {
|
|
||||||
self.database_name
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
/// Returns the table name from which the column originates
|
|
||||||
pub fn table_name(&self) -> Option<&str> {
|
|
||||||
self.table_name
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
/// Returns the column name from which the column originates
|
|
||||||
pub fn origin_name(&self) -> Option<&str> {
|
|
||||||
self.origin_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Statement<'_> {
|
|
||||||
/// Get all the column names in the result set of the prepared statement.
|
|
||||||
///
|
|
||||||
/// If associated DB schema can be altered concurrently, you should make
|
|
||||||
/// sure that current statement has already been stepped once before
|
|
||||||
/// calling this method.
|
|
||||||
pub fn column_names(&self) -> Vec<&str> {
|
|
||||||
let n = self.column_count();
|
|
||||||
let mut cols = Vec::with_capacity(n);
|
|
||||||
for i in 0..n {
|
|
||||||
let s = self.column_name_unwrap(i);
|
|
||||||
cols.push(s);
|
|
||||||
}
|
|
||||||
cols
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the number of columns in the result set returned by the prepared
|
|
||||||
/// statement.
|
|
||||||
///
|
|
||||||
/// If associated DB schema can be altered concurrently, you should make
|
|
||||||
/// sure that current statement has already been stepped once before
|
|
||||||
/// calling this method.
|
|
||||||
#[inline]
|
|
||||||
pub fn column_count(&self) -> usize {
|
|
||||||
self.stmt.column_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that column name reference lifetime is limited:
|
|
||||||
/// <https://www.sqlite.org/c3ref/column_name.html>
|
|
||||||
/// > The returned string pointer is valid...
|
|
||||||
///
|
|
||||||
/// `column_name` reference can become invalid if `stmt` is reprepared
|
|
||||||
/// (because of schema change) when `query_row` is called. So we assert
|
|
||||||
/// that a compilation error happens if this reference is kept alive:
|
|
||||||
/// ```compile_fail
|
|
||||||
/// use rusqlite::{Connection, Result};
|
|
||||||
/// fn main() -> Result<()> {
|
|
||||||
/// let db = Connection::open_in_memory()?;
|
|
||||||
/// let mut stmt = db.prepare("SELECT 1 as x")?;
|
|
||||||
/// let column_name = stmt.column_name(0)?;
|
|
||||||
/// let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
|
|
||||||
/// assert_eq!(1, x);
|
|
||||||
/// assert_eq!("x", column_name);
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
|
|
||||||
// Just panic if the bounds are wrong for now, we never call this
|
|
||||||
// without checking first.
|
|
||||||
self.column_name(col).expect("Column out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the name assigned to a particular column in the result set
|
|
||||||
/// returned by the prepared statement.
|
|
||||||
///
|
|
||||||
/// If associated DB schema can be altered concurrently, you should make
|
|
||||||
/// sure that current statement has already been stepped once before
|
|
||||||
/// calling this method.
|
|
||||||
///
|
|
||||||
/// ## Failure
|
|
||||||
///
|
|
||||||
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
|
||||||
/// column range for this row.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics when column name is not valid UTF-8.
|
|
||||||
#[inline]
|
|
||||||
pub fn column_name(&self, col: usize) -> Result<&str> {
|
|
||||||
self.stmt
|
|
||||||
.column_name(col)
|
|
||||||
// clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable)
|
|
||||||
.ok_or(Error::InvalidColumnIndex(col))
|
|
||||||
.map(|slice| {
|
|
||||||
slice
|
|
||||||
.to_str()
|
|
||||||
.expect("Invalid UTF-8 sequence in column name")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the column index in the result set for a given column name.
|
|
||||||
///
|
|
||||||
/// If there is no AS clause then the name of the column is unspecified and
|
|
||||||
/// may change from one release of SQLite to the next.
|
|
||||||
///
|
|
||||||
/// If associated DB schema can be altered concurrently, you should make
|
|
||||||
/// sure that current statement has already been stepped once before
|
|
||||||
/// calling this method.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return an `Error::InvalidColumnName` when there is no column with
|
|
||||||
/// the specified `name`.
|
|
||||||
#[inline]
|
|
||||||
pub fn column_index(&self, name: &str) -> Result<usize> {
|
|
||||||
let bytes = name.as_bytes();
|
|
||||||
let n = self.column_count();
|
|
||||||
for i in 0..n {
|
|
||||||
// Note: `column_name` is only fallible if `i` is out of bounds,
|
|
||||||
// which we've already checked.
|
|
||||||
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
|
|
||||||
return Ok(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::InvalidColumnName(String::from(name)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a slice describing the columns of the result of the query.
|
|
||||||
///
|
|
||||||
/// If associated DB schema can be altered concurrently, you should make
|
|
||||||
/// sure that current statement has already been stepped once before
|
|
||||||
/// calling this method.
|
|
||||||
#[cfg(feature = "column_decltype")]
|
|
||||||
pub fn columns(&self) -> Vec<Column<'_>> {
|
|
||||||
let n = self.column_count();
|
|
||||||
let mut cols = Vec::with_capacity(n);
|
|
||||||
for i in 0..n {
|
|
||||||
let name = self.column_name_unwrap(i);
|
|
||||||
let slice = self.stmt.column_decltype(i);
|
|
||||||
let decl_type = slice.map(|s| {
|
|
||||||
s.to_str()
|
|
||||||
.expect("Invalid UTF-8 sequence in column declaration")
|
|
||||||
});
|
|
||||||
cols.push(Column { name, decl_type });
|
|
||||||
}
|
|
||||||
cols
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the names of the database, table, and row from which
|
|
||||||
/// each column of this query's results originate.
|
|
||||||
///
|
|
||||||
/// Computed or otherwise derived columns will have None values for these fields.
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
pub fn columns_with_metadata(&self) -> Vec<ColumnMetadata<'_>> {
|
|
||||||
let n = self.column_count();
|
|
||||||
let mut col_mets = Vec::with_capacity(n);
|
|
||||||
for i in 0..n {
|
|
||||||
let name = self.column_name_unwrap(i);
|
|
||||||
let db_slice = self.stmt.column_database_name(i);
|
|
||||||
let tbl_slice = self.stmt.column_table_name(i);
|
|
||||||
let origin_slice = self.stmt.column_origin_name(i);
|
|
||||||
col_mets.push(ColumnMetadata {
|
|
||||||
name,
|
|
||||||
database_name: db_slice.map(|s| {
|
|
||||||
s.to_str()
|
|
||||||
.expect("Invalid UTF-8 sequence in column db name")
|
|
||||||
}),
|
|
||||||
table_name: tbl_slice.map(|s| {
|
|
||||||
s.to_str()
|
|
||||||
.expect("Invalid UTF-8 sequence in column table name")
|
|
||||||
}),
|
|
||||||
origin_name: origin_slice.map(|s| {
|
|
||||||
s.to_str()
|
|
||||||
.expect("Invalid UTF-8 sequence in column origin name")
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
col_mets
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract metadata of column at specified index
|
|
||||||
///
|
|
||||||
/// Returns:
|
|
||||||
/// - database name
|
|
||||||
/// - table name
|
|
||||||
/// - original column name
|
|
||||||
/// - declared data type
|
|
||||||
/// - name of default collation sequence
|
|
||||||
/// - True if column has a NOT NULL constraint
|
|
||||||
/// - True if column is part of the PRIMARY KEY
|
|
||||||
/// - True if column is AUTOINCREMENT
|
|
||||||
///
|
|
||||||
/// See [Connection::column_metadata]
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
#[expect(clippy::type_complexity)]
|
|
||||||
pub fn column_metadata(
|
|
||||||
&self,
|
|
||||||
col: usize,
|
|
||||||
) -> Result<
|
|
||||||
Option<(
|
|
||||||
&CStr,
|
|
||||||
&CStr,
|
|
||||||
&CStr,
|
|
||||||
Option<&CStr>,
|
|
||||||
Option<&CStr>,
|
|
||||||
bool,
|
|
||||||
bool,
|
|
||||||
bool,
|
|
||||||
)>,
|
|
||||||
> {
|
|
||||||
let db_name = self.stmt.column_database_name(col);
|
|
||||||
let table_name = self.stmt.column_table_name(col);
|
|
||||||
let origin_name = self.stmt.column_origin_name(col);
|
|
||||||
if db_name.is_none() || table_name.is_none() || origin_name.is_none() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let (data_type, coll_seq, not_null, primary_key, auto_inc) =
|
|
||||||
self.conn
|
|
||||||
.column_metadata(db_name, table_name.unwrap(), origin_name.unwrap())?;
|
|
||||||
Ok(Some((
|
|
||||||
db_name.unwrap(),
|
|
||||||
table_name.unwrap(),
|
|
||||||
origin_name.unwrap(),
|
|
||||||
data_type,
|
|
||||||
coll_seq,
|
|
||||||
not_null,
|
|
||||||
primary_key,
|
|
||||||
auto_inc,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Check if `table_name`.`column_name` exists.
|
|
||||||
///
|
|
||||||
/// `db_name` is main, temp, the name in ATTACH, or `None` to search all databases.
|
|
||||||
pub fn column_exists<N: Name>(
|
|
||||||
&self,
|
|
||||||
db_name: Option<N>,
|
|
||||||
table_name: N,
|
|
||||||
column_name: N,
|
|
||||||
) -> Result<bool> {
|
|
||||||
self.exists(db_name, table_name, Some(column_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if `table_name` exists.
|
|
||||||
///
|
|
||||||
/// `db_name` is main, temp, the name in ATTACH, or `None` to search all databases.
|
|
||||||
pub fn table_exists<N: Name>(&self, db_name: Option<N>, table_name: N) -> Result<bool> {
|
|
||||||
self.exists(db_name, table_name, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract metadata of column at specified index
|
|
||||||
///
|
|
||||||
/// Returns:
|
|
||||||
/// - declared data type
|
|
||||||
/// - name of default collation sequence
|
|
||||||
/// - True if column has a NOT NULL constraint
|
|
||||||
/// - True if column is part of the PRIMARY KEY
|
|
||||||
/// - True if column is AUTOINCREMENT
|
|
||||||
#[expect(clippy::type_complexity)]
|
|
||||||
pub fn column_metadata<N: Name>(
|
|
||||||
&self,
|
|
||||||
db_name: Option<N>,
|
|
||||||
table_name: N,
|
|
||||||
column_name: N,
|
|
||||||
) -> Result<(Option<&CStr>, Option<&CStr>, bool, bool, bool)> {
|
|
||||||
let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
|
|
||||||
let db_name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
|
||||||
let table_name = table_name.as_cstr()?;
|
|
||||||
let column_name = column_name.as_cstr()?;
|
|
||||||
|
|
||||||
let mut data_type: *const c_char = ptr::null_mut();
|
|
||||||
let mut coll_seq: *const c_char = ptr::null_mut();
|
|
||||||
let mut not_null = 0;
|
|
||||||
let mut primary_key = 0;
|
|
||||||
let mut auto_inc = 0;
|
|
||||||
|
|
||||||
self.decode_result(unsafe {
|
|
||||||
ffi::sqlite3_table_column_metadata(
|
|
||||||
self.handle(),
|
|
||||||
db_name,
|
|
||||||
table_name.as_ptr(),
|
|
||||||
column_name.as_ptr(),
|
|
||||||
&mut data_type,
|
|
||||||
&mut coll_seq,
|
|
||||||
&mut not_null,
|
|
||||||
&mut primary_key,
|
|
||||||
&mut auto_inc,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
if data_type.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(unsafe { CStr::from_ptr(data_type) })
|
|
||||||
},
|
|
||||||
if coll_seq.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(unsafe { CStr::from_ptr(coll_seq) })
|
|
||||||
},
|
|
||||||
not_null != 0,
|
|
||||||
primary_key != 0,
|
|
||||||
auto_inc != 0,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exists<N: Name>(
|
|
||||||
&self,
|
|
||||||
db_name: Option<N>,
|
|
||||||
table_name: N,
|
|
||||||
column_name: Option<N>,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
|
|
||||||
let db_name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
|
||||||
let table_name = table_name.as_cstr()?;
|
|
||||||
let cn = column_name.as_ref().map(N::as_cstr).transpose()?;
|
|
||||||
let column_name = cn.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
|
||||||
let r = unsafe {
|
|
||||||
ffi::sqlite3_table_column_metadata(
|
|
||||||
self.handle(),
|
|
||||||
db_name,
|
|
||||||
table_name.as_ptr(),
|
|
||||||
column_name,
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
match r {
|
|
||||||
ffi::SQLITE_OK => Ok(true),
|
|
||||||
ffi::SQLITE_ERROR => Ok(false),
|
|
||||||
_ => self.db.borrow().decode_result(r).map(|_| false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "column_decltype")]
|
|
||||||
fn test_columns() -> Result<()> {
|
|
||||||
use super::Column;
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let query = db.prepare("SELECT * FROM sqlite_master")?;
|
|
||||||
let columns = query.columns();
|
|
||||||
let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
|
|
||||||
assert_eq!(
|
|
||||||
column_names.as_slice(),
|
|
||||||
&["type", "name", "tbl_name", "rootpage", "sql"]
|
|
||||||
);
|
|
||||||
let column_types: Vec<Option<String>> = columns
|
|
||||||
.iter()
|
|
||||||
.map(|col| col.decl_type().map(str::to_lowercase))
|
|
||||||
.collect();
|
|
||||||
assert_eq!(
|
|
||||||
&column_types[..3],
|
|
||||||
&[
|
|
||||||
Some("text".to_owned()),
|
|
||||||
Some("text".to_owned()),
|
|
||||||
Some("text".to_owned()),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
fn test_columns_with_metadata() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
|
|
||||||
|
|
||||||
let col_mets = query.columns_with_metadata();
|
|
||||||
|
|
||||||
assert_eq!(col_mets.len(), 6);
|
|
||||||
|
|
||||||
for col in col_mets.iter().take(5) {
|
|
||||||
assert_eq!(&col.database_name(), &Some("main"));
|
|
||||||
assert_eq!(&col.table_name(), &Some("sqlite_master"));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(col_mets[5].database_name().is_none());
|
|
||||||
assert!(col_mets[5].table_name().is_none());
|
|
||||||
assert!(col_mets[5].origin_name().is_none());
|
|
||||||
|
|
||||||
let col_origins: Vec<Option<&str>> = col_mets.iter().map(|col| col.origin_name()).collect();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&col_origins[..5],
|
|
||||||
&[
|
|
||||||
Some("type"),
|
|
||||||
Some("name"),
|
|
||||||
Some("tbl_name"),
|
|
||||||
Some("rootpage"),
|
|
||||||
Some("sql"),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_column_name_in_error() -> Result<()> {
|
|
||||||
use crate::{types::Type, Error};
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch(
|
|
||||||
"BEGIN;
|
|
||||||
CREATE TABLE foo(x INTEGER, y TEXT);
|
|
||||||
INSERT INTO foo VALUES(4, NULL);
|
|
||||||
END;",
|
|
||||||
)?;
|
|
||||||
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
let row = rows.next()?.unwrap();
|
|
||||||
match row.get::<_, String>(0).unwrap_err() {
|
|
||||||
Error::InvalidColumnType(idx, name, ty) => {
|
|
||||||
assert_eq!(idx, 0);
|
|
||||||
assert_eq!(name, "renamed");
|
|
||||||
assert_eq!(ty, Type::Integer);
|
|
||||||
}
|
|
||||||
e => {
|
|
||||||
panic!("Unexpected error type: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match row.get::<_, String>("y").unwrap_err() {
|
|
||||||
Error::InvalidColumnType(idx, name, ty) => {
|
|
||||||
assert_eq!(idx, 1);
|
|
||||||
assert_eq!(name, "y");
|
|
||||||
assert_eq!(ty, Type::Null);
|
|
||||||
}
|
|
||||||
e => {
|
|
||||||
panic!("Unexpected error type: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `column_name` reference should stay valid until `stmt` is reprepared (or
|
|
||||||
/// reset) even if DB schema is altered (SQLite documentation is
|
|
||||||
/// ambiguous here because it says reference "is valid until (...) the next
|
|
||||||
/// call to `sqlite3_column_name()` or `sqlite3_column_name16()` on the same
|
|
||||||
/// column.". We assume that reference is valid if only
|
|
||||||
/// `sqlite3_column_name()` is used):
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
fn test_column_name_reference() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE y (x);")?;
|
|
||||||
let stmt = db.prepare("SELECT x FROM y;")?;
|
|
||||||
let column_name = stmt.column_name(0)?;
|
|
||||||
assert_eq!("x", column_name);
|
|
||||||
db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
|
|
||||||
// column name is not refreshed until statement is re-prepared
|
|
||||||
let same_column_name = stmt.column_name(0)?;
|
|
||||||
assert_eq!(same_column_name, column_name);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
fn stmt_column_metadata() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
|
|
||||||
let (db_name, table_name, col_name, data_type, coll_seq, not_null, primary_key, auto_inc) =
|
|
||||||
query.column_metadata(0)?.unwrap();
|
|
||||||
assert_eq!(db_name, crate::MAIN_DB);
|
|
||||||
assert_eq!(table_name, c"sqlite_master");
|
|
||||||
assert_eq!(col_name, c"type");
|
|
||||||
assert_eq!(data_type, Some(c"TEXT"));
|
|
||||||
assert_eq!(coll_seq, Some(c"BINARY"));
|
|
||||||
assert!(!not_null);
|
|
||||||
assert!(!primary_key);
|
|
||||||
assert!(!auto_inc);
|
|
||||||
assert!(query.column_metadata(5)?.is_none());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn column_exists() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
assert!(db.column_exists(None, c"sqlite_master", c"type")?);
|
|
||||||
assert!(db.column_exists(Some(crate::TEMP_DB), c"sqlite_master", c"type")?);
|
|
||||||
assert!(!db.column_exists(Some(crate::MAIN_DB), c"sqlite_temp_master", c"type")?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_exists() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
assert!(db.table_exists(None, c"sqlite_master")?);
|
|
||||||
assert!(db.table_exists(Some(crate::TEMP_DB), c"sqlite_master")?);
|
|
||||||
assert!(!db.table_exists(Some(crate::MAIN_DB), c"sqlite_temp_master")?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn column_metadata() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let (data_type, coll_seq, not_null, primary_key, auto_inc) =
|
|
||||||
db.column_metadata(None, c"sqlite_master", c"type")?;
|
|
||||||
assert_eq!(
|
|
||||||
data_type.map(|cs| cs.to_str().unwrap().to_ascii_uppercase()),
|
|
||||||
Some("TEXT".to_owned())
|
|
||||||
);
|
|
||||||
assert_eq!(coll_seq, Some(c"BINARY"));
|
|
||||||
assert!(!not_null);
|
|
||||||
assert!(!primary_key);
|
|
||||||
assert!(!auto_inc);
|
|
||||||
assert!(db.column_metadata(None, c"sqlite_master", c"foo").is_err());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
vendor/rusqlite/src/config.rs
vendored
169
vendor/rusqlite/src/config.rs
vendored
@@ -1,169 +0,0 @@
|
|||||||
//! Configure database connections
|
|
||||||
|
|
||||||
use std::ffi::c_int;
|
|
||||||
|
|
||||||
use crate::error::check;
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
/// Database Connection Configuration Options
|
|
||||||
/// See [Database Connection Configuration Options](https://sqlite.org/c3ref/c_dbconfig_enable_fkey.html) for details.
|
|
||||||
#[repr(i32)]
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
#[expect(non_camel_case_types)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum DbConfig {
|
|
||||||
//SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
|
|
||||||
//SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */
|
|
||||||
/// Enable or disable the enforcement of foreign key constraints.
|
|
||||||
SQLITE_DBCONFIG_ENABLE_FKEY = ffi::SQLITE_DBCONFIG_ENABLE_FKEY,
|
|
||||||
/// Enable or disable triggers.
|
|
||||||
SQLITE_DBCONFIG_ENABLE_TRIGGER = ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER,
|
|
||||||
/// Enable or disable the `fts3_tokenizer()` function which is part of the
|
|
||||||
/// FTS3 full-text search engine extension.
|
|
||||||
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, // 3.12.0
|
|
||||||
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
|
|
||||||
/// In WAL mode, enable or disable the checkpoint operation before closing
|
|
||||||
/// the connection.
|
|
||||||
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // 3.16.2
|
|
||||||
/// Activates or deactivates the query planner stability guarantee (QPSG).
|
|
||||||
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
|
||||||
/// Includes or excludes output for any operations performed by trigger
|
|
||||||
/// programs from the output of EXPLAIN QUERY PLAN commands.
|
|
||||||
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
|
|
||||||
/// Activates or deactivates the "reset" flag for a database connection.
|
|
||||||
/// Run VACUUM with this flag set to reset the database.
|
|
||||||
SQLITE_DBCONFIG_RESET_DATABASE = 1009, // 3.24.0
|
|
||||||
/// Activates or deactivates the "defensive" flag for a database connection.
|
|
||||||
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
|
|
||||||
/// Activates or deactivates the `writable_schema` flag.
|
|
||||||
SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011, // 3.28.0
|
|
||||||
/// Activates or deactivates the legacy behavior of the ALTER TABLE RENAME
|
|
||||||
/// command.
|
|
||||||
SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012, // 3.29
|
|
||||||
/// Activates or deactivates the legacy double-quoted string literal
|
|
||||||
/// misfeature for DML statements only.
|
|
||||||
SQLITE_DBCONFIG_DQS_DML = 1013, // 3.29.0
|
|
||||||
/// Activates or deactivates the legacy double-quoted string literal
|
|
||||||
/// misfeature for DDL statements.
|
|
||||||
SQLITE_DBCONFIG_DQS_DDL = 1014, // 3.29.0
|
|
||||||
/// Enable or disable views.
|
|
||||||
SQLITE_DBCONFIG_ENABLE_VIEW = 1015, // 3.30.0
|
|
||||||
/// Activates or deactivates the legacy file format flag.
|
|
||||||
SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016, // 3.31.0
|
|
||||||
/// Tells SQLite to assume that database schemas (the contents of the
|
|
||||||
/// `sqlite_master` tables) are untainted by malicious content.
|
|
||||||
SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017, // 3.31.0
|
|
||||||
/// Sets or clears a flag that enables collection of the
|
|
||||||
/// `sqlite3_stmt_scanstatus_v2()` statistics
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018, // 3.42.0
|
|
||||||
/// Changes the default order in which tables and indexes are scanned
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019, // 3.42.0
|
|
||||||
/// Enables or disables the ability of the ATTACH DATABASE SQL command
|
|
||||||
/// to create a new database file if the database filed named in the ATTACH command does not already exist.
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE = 1020, // 3.49.0
|
|
||||||
/// Enables or disables the ability of the ATTACH DATABASE SQL command to open a database for writing.
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE = 1021, // 3.49.0
|
|
||||||
/// Enables or disables the ability to include comments in SQL text.
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
SQLITE_DBCONFIG_ENABLE_COMMENTS = 1022, // 3.49.0
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Returns the current value of a `config`.
|
|
||||||
///
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_FKEY`: return `false` or `true` to indicate
|
|
||||||
/// whether FK enforcement is off or on
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: return `false` or `true` to indicate
|
|
||||||
/// whether triggers are disabled or enabled
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: return `false` or `true` to
|
|
||||||
/// indicate whether `fts3_tokenizer` are disabled or enabled
|
|
||||||
/// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: return `false` to indicate
|
|
||||||
/// checkpoints-on-close are not disabled or `true` if they are
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_QPSG`: return `false` or `true` to indicate
|
|
||||||
/// whether the QPSG is disabled or enabled
|
|
||||||
/// - `SQLITE_DBCONFIG_TRIGGER_EQP`: return `false` to indicate
|
|
||||||
/// output-for-trigger are not disabled or `true` if it is
|
|
||||||
#[inline]
|
|
||||||
pub fn db_config(&self, config: DbConfig) -> Result<bool> {
|
|
||||||
let c = self.db.borrow();
|
|
||||||
unsafe {
|
|
||||||
let mut val = 0;
|
|
||||||
check(ffi::sqlite3_db_config(
|
|
||||||
c.db(),
|
|
||||||
config as c_int,
|
|
||||||
-1,
|
|
||||||
&mut val,
|
|
||||||
))?;
|
|
||||||
Ok(val != 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make configuration changes to a database connection
|
|
||||||
///
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_FKEY`: `false` to disable FK enforcement,
|
|
||||||
/// `true` to enable FK enforcement
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: `false` to disable triggers, `true`
|
|
||||||
/// to enable triggers
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: `false` to disable
|
|
||||||
/// `fts3_tokenizer()`, `true` to enable `fts3_tokenizer()`
|
|
||||||
/// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: `false` (the default) to enable
|
|
||||||
/// checkpoints-on-close, `true` to disable them
|
|
||||||
/// - `SQLITE_DBCONFIG_ENABLE_QPSG`: `false` to disable the QPSG, `true` to
|
|
||||||
/// enable QPSG
|
|
||||||
/// - `SQLITE_DBCONFIG_TRIGGER_EQP`: `false` to disable output for trigger
|
|
||||||
/// programs, `true` to enable it
|
|
||||||
#[inline]
|
|
||||||
pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result<bool> {
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
unsafe {
|
|
||||||
let mut val = 0;
|
|
||||||
check(ffi::sqlite3_db_config(
|
|
||||||
c.db(),
|
|
||||||
config as c_int,
|
|
||||||
new_val as c_int,
|
|
||||||
&mut val,
|
|
||||||
))?;
|
|
||||||
Ok(val != 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::DbConfig;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_db_config() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY)?;
|
|
||||||
assert_eq!(
|
|
||||||
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite),
|
|
||||||
Ok(opposite)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY),
|
|
||||||
Ok(opposite)
|
|
||||||
);
|
|
||||||
|
|
||||||
let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)?;
|
|
||||||
assert_eq!(
|
|
||||||
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite),
|
|
||||||
Ok(opposite)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER),
|
|
||||||
Ok(opposite)
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
62
vendor/rusqlite/src/context.rs
vendored
62
vendor/rusqlite/src/context.rs
vendored
@@ -1,62 +0,0 @@
|
|||||||
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
|
|
||||||
|
|
||||||
use crate::ffi::sqlite3_value;
|
|
||||||
use std::ffi::c_void;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::ffi::sqlite3_context;
|
|
||||||
|
|
||||||
use crate::str_for_sqlite;
|
|
||||||
use crate::types::{ToSqlOutput, ValueRef};
|
|
||||||
|
|
||||||
// This function is inline despite it's size because what's in the ToSqlOutput
|
|
||||||
// is often known to the compiler, and thus const prop/DCE can substantially
|
|
||||||
// simplify the function.
|
|
||||||
#[inline]
|
|
||||||
pub(super) unsafe fn set_result(
|
|
||||||
ctx: *mut sqlite3_context,
|
|
||||||
#[allow(unused_variables)] args: &[*mut sqlite3_value],
|
|
||||||
result: &ToSqlOutput<'_>,
|
|
||||||
) {
|
|
||||||
let value = match *result {
|
|
||||||
ToSqlOutput::Borrowed(v) => v,
|
|
||||||
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
|
||||||
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
ToSqlOutput::ZeroBlob(len) => {
|
|
||||||
// TODO sqlite3_result_zeroblob64 // 3.8.11
|
|
||||||
return ffi::sqlite3_result_zeroblob(ctx, len);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
ToSqlOutput::Arg(i) => {
|
|
||||||
return ffi::sqlite3_result_value(ctx, args[i]);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "pointer")]
|
|
||||||
ToSqlOutput::Pointer(ref p) => {
|
|
||||||
return ffi::sqlite3_result_pointer(ctx, p.0 as _, p.1.as_ptr(), p.2);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match value {
|
|
||||||
ValueRef::Null => ffi::sqlite3_result_null(ctx),
|
|
||||||
ValueRef::Integer(i) => ffi::sqlite3_result_int64(ctx, i),
|
|
||||||
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
|
|
||||||
ValueRef::Text(s) => {
|
|
||||||
let (c_str, len, destructor) = str_for_sqlite(s);
|
|
||||||
ffi::sqlite3_result_text64(ctx, c_str, len, destructor, ffi::SQLITE_UTF8 as _);
|
|
||||||
}
|
|
||||||
ValueRef::Blob(b) => {
|
|
||||||
let length = b.len();
|
|
||||||
if length == 0 {
|
|
||||||
ffi::sqlite3_result_zeroblob(ctx, 0);
|
|
||||||
} else {
|
|
||||||
ffi::sqlite3_result_blob64(
|
|
||||||
ctx,
|
|
||||||
b.as_ptr().cast::<c_void>(),
|
|
||||||
length as ffi::sqlite3_uint64,
|
|
||||||
ffi::SQLITE_TRANSIENT(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
514
vendor/rusqlite/src/error.rs
vendored
514
vendor/rusqlite/src/error.rs
vendored
@@ -1,514 +0,0 @@
|
|||||||
use crate::types::FromSqlError;
|
|
||||||
use crate::types::Type;
|
|
||||||
use crate::{errmsg_to_string, ffi, Result};
|
|
||||||
use std::error;
|
|
||||||
use std::ffi::{c_char, c_int, NulError};
|
|
||||||
use std::fmt;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
/// Enum listing possible errors from rusqlite.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Error {
|
|
||||||
/// An error from an underlying SQLite call.
|
|
||||||
SqliteFailure(ffi::Error, Option<String>),
|
|
||||||
|
|
||||||
/// Error reported when attempting to open a connection when SQLite was
|
|
||||||
/// configured to allow single-threaded use only.
|
|
||||||
SqliteSingleThreadedMode,
|
|
||||||
|
|
||||||
/// Error when the value of a particular column is requested, but it cannot
|
|
||||||
/// be converted to the requested Rust type.
|
|
||||||
FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync + 'static>),
|
|
||||||
|
|
||||||
/// Error when SQLite gives us an integral value outside the range of the
|
|
||||||
/// requested type (e.g., trying to get the value 1000 into a `u8`).
|
|
||||||
/// The associated `usize` is the column index,
|
|
||||||
/// and the associated `i64` is the value returned by SQLite.
|
|
||||||
IntegralValueOutOfRange(usize, i64),
|
|
||||||
|
|
||||||
/// Error converting a string to UTF-8.
|
|
||||||
Utf8Error(usize, str::Utf8Error),
|
|
||||||
|
|
||||||
/// Error converting a string to a C-compatible string because it contained
|
|
||||||
/// an embedded nul.
|
|
||||||
NulError(NulError),
|
|
||||||
|
|
||||||
/// Error when using SQL named parameters and passing a parameter name not
|
|
||||||
/// present in the SQL.
|
|
||||||
InvalidParameterName(String),
|
|
||||||
|
|
||||||
/// Error converting a file path to a string.
|
|
||||||
InvalidPath(PathBuf),
|
|
||||||
|
|
||||||
/// Error returned when an [`execute`](crate::Connection::execute) call
|
|
||||||
/// returns rows.
|
|
||||||
ExecuteReturnedResults,
|
|
||||||
|
|
||||||
/// Error when a query that was expected to return at least one row (e.g.,
|
|
||||||
/// for [`query_row`](crate::Connection::query_row)) did not return any.
|
|
||||||
QueryReturnedNoRows,
|
|
||||||
|
|
||||||
/// Error when a query that was expected to return only one row (e.g.,
|
|
||||||
/// for [`query_one`](crate::Connection::query_one)) did return more than one.
|
|
||||||
QueryReturnedMoreThanOneRow,
|
|
||||||
|
|
||||||
/// Error when the value of a particular column is requested, but the index
|
|
||||||
/// is out of range for the statement.
|
|
||||||
InvalidColumnIndex(usize),
|
|
||||||
|
|
||||||
/// Error when the value of a named column is requested, but no column
|
|
||||||
/// matches the name for the statement.
|
|
||||||
InvalidColumnName(String),
|
|
||||||
|
|
||||||
/// Error when the value of a particular column is requested, but the type
|
|
||||||
/// of the result in that column cannot be converted to the requested
|
|
||||||
/// Rust type.
|
|
||||||
InvalidColumnType(usize, String, Type),
|
|
||||||
|
|
||||||
/// Error when a query that was expected to insert one row did not insert
|
|
||||||
/// any or insert many.
|
|
||||||
StatementChangedRows(usize),
|
|
||||||
|
|
||||||
/// Error returned by
|
|
||||||
/// [`functions::Context::get`](crate::functions::Context::get) when the
|
|
||||||
/// function argument cannot be converted to the requested type.
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
InvalidFunctionParameterType(usize, Type),
|
|
||||||
/// Error returned by [`vtab::Values::get`](crate::vtab::Values::get) when
|
|
||||||
/// the filter argument cannot be converted to the requested type.
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
InvalidFilterParameterType(usize, Type),
|
|
||||||
|
|
||||||
/// An error case available for implementors of custom user functions (e.g.,
|
|
||||||
/// [`create_scalar_function`](crate::Connection::create_scalar_function)).
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
UserFunctionError(Box<dyn error::Error + Send + Sync + 'static>),
|
|
||||||
|
|
||||||
/// Error available for the implementors of the
|
|
||||||
/// [`ToSql`](crate::types::ToSql) trait.
|
|
||||||
ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
|
|
||||||
|
|
||||||
/// Error when the SQL is not a `SELECT`, is not read-only.
|
|
||||||
InvalidQuery,
|
|
||||||
|
|
||||||
/// An error case available for implementors of custom modules (e.g.,
|
|
||||||
/// [`create_module`](crate::Connection::create_module)).
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
ModuleError(String),
|
|
||||||
|
|
||||||
/// An unwinding panic occurs in a UDF (user-defined function).
|
|
||||||
UnwindingPanic,
|
|
||||||
|
|
||||||
/// An error returned when
|
|
||||||
/// [`Context::get_aux`](crate::functions::Context::get_aux) attempts to
|
|
||||||
/// retrieve data of a different type than what had been stored using
|
|
||||||
/// [`Context::set_aux`](crate::functions::Context::set_aux).
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
GetAuxWrongType,
|
|
||||||
|
|
||||||
/// Error when the SQL contains multiple statements.
|
|
||||||
MultipleStatement,
|
|
||||||
/// Error when the number of bound parameters does not match the number of
|
|
||||||
/// parameters in the query. The first `usize` is how many parameters were
|
|
||||||
/// given, the 2nd is how many were expected.
|
|
||||||
InvalidParameterCount(usize, usize),
|
|
||||||
|
|
||||||
/// Returned from various functions in the Blob IO positional API. For
|
|
||||||
/// example,
|
|
||||||
/// [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact) will
|
|
||||||
/// return it if the blob has insufficient data.
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
BlobSizeError,
|
|
||||||
/// Error referencing a specific token in the input SQL
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.38.0
|
|
||||||
SqlInputError {
|
|
||||||
/// error code
|
|
||||||
error: ffi::Error,
|
|
||||||
/// error message
|
|
||||||
msg: String,
|
|
||||||
/// SQL input
|
|
||||||
sql: String,
|
|
||||||
/// byte offset of the start of invalid token
|
|
||||||
offset: c_int,
|
|
||||||
},
|
|
||||||
/// Loadable extension initialization error
|
|
||||||
#[cfg(feature = "loadable_extension")]
|
|
||||||
InitError(ffi::InitError),
|
|
||||||
/// Error when the schema of a particular database is requested, but the index
|
|
||||||
/// is out of range.
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.39.0
|
|
||||||
InvalidDatabaseIndex(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Error {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::SqliteFailure(e1, s1), Self::SqliteFailure(e2, s2)) => e1 == e2 && s1 == s2,
|
|
||||||
(Self::SqliteSingleThreadedMode, Self::SqliteSingleThreadedMode) => true,
|
|
||||||
(Self::IntegralValueOutOfRange(i1, n1), Self::IntegralValueOutOfRange(i2, n2)) => {
|
|
||||||
i1 == i2 && n1 == n2
|
|
||||||
}
|
|
||||||
(Self::Utf8Error(i1, e1), Self::Utf8Error(i2, e2)) => i1 == i2 && e1 == e2,
|
|
||||||
(Self::NulError(e1), Self::NulError(e2)) => e1 == e2,
|
|
||||||
(Self::InvalidParameterName(n1), Self::InvalidParameterName(n2)) => n1 == n2,
|
|
||||||
(Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2,
|
|
||||||
(Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true,
|
|
||||||
(Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true,
|
|
||||||
(Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true,
|
|
||||||
(Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2,
|
|
||||||
(Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2,
|
|
||||||
(Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => {
|
|
||||||
i1 == i2 && t1 == t2 && n1 == n2
|
|
||||||
}
|
|
||||||
(Self::StatementChangedRows(n1), Self::StatementChangedRows(n2)) => n1 == n2,
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
(
|
|
||||||
Self::InvalidFunctionParameterType(i1, t1),
|
|
||||||
Self::InvalidFunctionParameterType(i2, t2),
|
|
||||||
) => i1 == i2 && t1 == t2,
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
(
|
|
||||||
Self::InvalidFilterParameterType(i1, t1),
|
|
||||||
Self::InvalidFilterParameterType(i2, t2),
|
|
||||||
) => i1 == i2 && t1 == t2,
|
|
||||||
(Self::InvalidQuery, Self::InvalidQuery) => true,
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
(Self::ModuleError(s1), Self::ModuleError(s2)) => s1 == s2,
|
|
||||||
(Self::UnwindingPanic, Self::UnwindingPanic) => true,
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
(Self::GetAuxWrongType, Self::GetAuxWrongType) => true,
|
|
||||||
(Self::InvalidParameterCount(i1, n1), Self::InvalidParameterCount(i2, n2)) => {
|
|
||||||
i1 == i2 && n1 == n2
|
|
||||||
}
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
(Self::BlobSizeError, Self::BlobSizeError) => true,
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
(
|
|
||||||
Self::SqlInputError {
|
|
||||||
error: e1,
|
|
||||||
msg: m1,
|
|
||||||
sql: s1,
|
|
||||||
offset: o1,
|
|
||||||
},
|
|
||||||
Self::SqlInputError {
|
|
||||||
error: e2,
|
|
||||||
msg: m2,
|
|
||||||
sql: s2,
|
|
||||||
offset: o2,
|
|
||||||
},
|
|
||||||
) => e1 == e2 && m1 == m2 && s1 == s2 && o1 == o2,
|
|
||||||
#[cfg(feature = "loadable_extension")]
|
|
||||||
(Self::InitError(e1), Self::InitError(e2)) => e1 == e2,
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
(Self::InvalidDatabaseIndex(i1), Self::InvalidDatabaseIndex(i2)) => i1 == i2,
|
|
||||||
(..) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<str::Utf8Error> for Error {
|
|
||||||
#[cold]
|
|
||||||
fn from(err: str::Utf8Error) -> Self {
|
|
||||||
Self::Utf8Error(UNKNOWN_COLUMN, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<NulError> for Error {
|
|
||||||
#[cold]
|
|
||||||
fn from(err: NulError) -> Self {
|
|
||||||
Self::NulError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const UNKNOWN_COLUMN: usize = usize::MAX;
|
|
||||||
|
|
||||||
/// The conversion isn't precise, but it's convenient to have it
|
|
||||||
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
|
|
||||||
impl From<FromSqlError> for Error {
|
|
||||||
#[cold]
|
|
||||||
fn from(err: FromSqlError) -> Self {
|
|
||||||
// The error type requires index and type fields, but they aren't known in this
|
|
||||||
// context.
|
|
||||||
match err {
|
|
||||||
FromSqlError::OutOfRange(val) => Self::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
|
|
||||||
FromSqlError::InvalidBlobSize { .. } => {
|
|
||||||
Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
|
||||||
}
|
|
||||||
FromSqlError::Other(source) => {
|
|
||||||
Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
|
|
||||||
}
|
|
||||||
_ => Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "loadable_extension")]
|
|
||||||
impl From<ffi::InitError> for Error {
|
|
||||||
#[cold]
|
|
||||||
fn from(err: ffi::InitError) -> Self {
|
|
||||||
Self::InitError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Self::SqliteFailure(ref err, None) => err.fmt(f),
|
|
||||||
Self::SqliteFailure(_, Some(ref s)) => write!(f, "{s}"),
|
|
||||||
Self::SqliteSingleThreadedMode => write!(
|
|
||||||
f,
|
|
||||||
"SQLite was compiled or configured for single-threaded use only"
|
|
||||||
),
|
|
||||||
Self::FromSqlConversionFailure(i, ref t, ref err) => {
|
|
||||||
if i != UNKNOWN_COLUMN {
|
|
||||||
write!(f, "Conversion error from type {t} at index: {i}, {err}")
|
|
||||||
} else {
|
|
||||||
err.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::IntegralValueOutOfRange(col, val) => {
|
|
||||||
if col != UNKNOWN_COLUMN {
|
|
||||||
write!(f, "Integer {val} out of range at index {col}")
|
|
||||||
} else {
|
|
||||||
write!(f, "Integer {val} out of range")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Utf8Error(col, ref err) => {
|
|
||||||
if col != UNKNOWN_COLUMN {
|
|
||||||
write!(f, "{err} at index {col}")
|
|
||||||
} else {
|
|
||||||
err.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::NulError(ref err) => err.fmt(f),
|
|
||||||
Self::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
|
|
||||||
Self::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
|
|
||||||
Self::ExecuteReturnedResults => {
|
|
||||||
write!(f, "Execute returned results - did you mean to call query?")
|
|
||||||
}
|
|
||||||
Self::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
|
||||||
Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"),
|
|
||||||
Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
|
|
||||||
Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
|
|
||||||
Self::InvalidColumnType(i, ref name, ref t) => {
|
|
||||||
write!(f, "Invalid column type {t} at index: {i}, name: {name}")
|
|
||||||
}
|
|
||||||
Self::InvalidParameterCount(i1, n1) => write!(
|
|
||||||
f,
|
|
||||||
"Wrong number of parameters passed to query. Got {i1}, needed {n1}"
|
|
||||||
),
|
|
||||||
Self::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
|
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Self::InvalidFunctionParameterType(i, ref t) => {
|
|
||||||
write!(f, "Invalid function parameter type {t} at index {i}")
|
|
||||||
}
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
Self::InvalidFilterParameterType(i, ref t) => {
|
|
||||||
write!(f, "Invalid filter parameter type {t} at index {i}")
|
|
||||||
}
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Self::UserFunctionError(ref err) => err.fmt(f),
|
|
||||||
Self::ToSqlConversionFailure(ref err) => err.fmt(f),
|
|
||||||
Self::InvalidQuery => write!(f, "Query is not read-only"),
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
Self::ModuleError(ref desc) => write!(f, "{desc}"),
|
|
||||||
Self::UnwindingPanic => write!(f, "unwinding panic"),
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Self::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
|
|
||||||
Self::MultipleStatement => write!(f, "Multiple statements provided"),
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
Self::BlobSizeError => "Blob size is insufficient".fmt(f),
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
Self::SqlInputError {
|
|
||||||
ref msg,
|
|
||||||
offset,
|
|
||||||
ref sql,
|
|
||||||
..
|
|
||||||
} => write!(f, "{msg} in {sql} at offset {offset}"),
|
|
||||||
#[cfg(feature = "loadable_extension")]
|
|
||||||
Self::InitError(ref err) => err.fmt(f),
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
Self::InvalidDatabaseIndex(i) => write!(f, "Invalid database index: {i}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Error {
|
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
||||||
match *self {
|
|
||||||
Self::SqliteFailure(ref err, _) => Some(err),
|
|
||||||
Self::Utf8Error(_, ref err) => Some(err),
|
|
||||||
Self::NulError(ref err) => Some(err),
|
|
||||||
|
|
||||||
Self::IntegralValueOutOfRange(..)
|
|
||||||
| Self::SqliteSingleThreadedMode
|
|
||||||
| Self::InvalidParameterName(_)
|
|
||||||
| Self::ExecuteReturnedResults
|
|
||||||
| Self::QueryReturnedNoRows
|
|
||||||
| Self::QueryReturnedMoreThanOneRow
|
|
||||||
| Self::InvalidColumnIndex(_)
|
|
||||||
| Self::InvalidColumnName(_)
|
|
||||||
| Self::InvalidColumnType(..)
|
|
||||||
| Self::InvalidPath(_)
|
|
||||||
| Self::InvalidParameterCount(..)
|
|
||||||
| Self::StatementChangedRows(_)
|
|
||||||
| Self::InvalidQuery
|
|
||||||
| Self::MultipleStatement => None,
|
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Self::InvalidFunctionParameterType(..) => None,
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
Self::InvalidFilterParameterType(..) => None,
|
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Self::UserFunctionError(ref err) => Some(&**err),
|
|
||||||
|
|
||||||
Self::FromSqlConversionFailure(_, _, ref err)
|
|
||||||
| Self::ToSqlConversionFailure(ref err) => Some(&**err),
|
|
||||||
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
Self::ModuleError(_) => None,
|
|
||||||
|
|
||||||
Self::UnwindingPanic => None,
|
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Self::GetAuxWrongType => None,
|
|
||||||
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
Self::BlobSizeError => None,
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
Self::SqlInputError { ref error, .. } => Some(error),
|
|
||||||
#[cfg(feature = "loadable_extension")]
|
|
||||||
Self::InitError(ref err) => Some(err),
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
Self::InvalidDatabaseIndex(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
/// Returns the underlying SQLite error if this is [`Error::SqliteFailure`].
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn sqlite_error(&self) -> Option<&ffi::Error> {
|
|
||||||
match self {
|
|
||||||
Self::SqliteFailure(error, _) => Some(error),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying SQLite error code if this is
|
|
||||||
/// [`Error::SqliteFailure`].
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> {
|
|
||||||
self.sqlite_error().map(|error| error.code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are public but not re-exported by lib.rs, so only visible within crate.
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
|
|
||||||
Error::SqliteFailure(ffi::Error::new(code), message)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! err {
|
|
||||||
($code:expr $(,)?) => {
|
|
||||||
$crate::error::error_from_sqlite_code($code, None)
|
|
||||||
};
|
|
||||||
($code:expr, $msg:literal $(,)?) => {
|
|
||||||
$crate::error::error_from_sqlite_code($code, Some(format!($msg)))
|
|
||||||
};
|
|
||||||
($code:expr, $err:expr $(,)?) => {
|
|
||||||
$crate::error::error_from_sqlite_code($code, Some(format!($err)))
|
|
||||||
};
|
|
||||||
($code:expr, $fmt:expr, $($arg:tt)*) => {
|
|
||||||
$crate::error::error_from_sqlite_code($code, Some(format!($fmt, $($arg)*)))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
|
|
||||||
error_from_sqlite_code(code, error_msg(db, code))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn error_msg(db: *mut ffi::sqlite3, code: c_int) -> Option<String> {
|
|
||||||
if db.is_null() || ffi::sqlite3_errcode(db) != code {
|
|
||||||
let err_str = ffi::sqlite3_errstr(code);
|
|
||||||
if err_str.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(errmsg_to_string(err_str))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(errmsg_to_string(ffi::sqlite3_errmsg(db)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> {
|
|
||||||
if code == ffi::SQLITE_OK {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(error_from_handle(db, code))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
#[cfg(not(feature = "modern_sqlite"))] // SQLite >= 3.38.0
|
|
||||||
pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, _sql: &str) -> Error {
|
|
||||||
error_from_handle(db, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
|
|
||||||
pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> Error {
|
|
||||||
if db.is_null() {
|
|
||||||
error_from_sqlite_code(code, None)
|
|
||||||
} else {
|
|
||||||
let error = ffi::Error::new(code);
|
|
||||||
let msg = error_msg(db, code);
|
|
||||||
if ffi::ErrorCode::Unknown == error.code {
|
|
||||||
let offset = ffi::sqlite3_error_offset(db);
|
|
||||||
if offset >= 0 {
|
|
||||||
return Error::SqlInputError {
|
|
||||||
error,
|
|
||||||
msg: msg.unwrap_or("error".to_owned()),
|
|
||||||
sql: sql.to_owned(),
|
|
||||||
offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Error::SqliteFailure(error, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check(code: c_int) -> Result<()> {
|
|
||||||
if code != ffi::SQLITE_OK {
|
|
||||||
Err(error_from_sqlite_code(code, None))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transform Rust error to SQLite error (message and code).
|
|
||||||
/// # Safety
|
|
||||||
/// This function is unsafe because it uses raw pointer
|
|
||||||
pub unsafe fn to_sqlite_error(e: &Error, err_msg: *mut *mut c_char) -> c_int {
|
|
||||||
use crate::util::alloc;
|
|
||||||
match e {
|
|
||||||
Error::SqliteFailure(err, s) => {
|
|
||||||
if let Some(s) = s {
|
|
||||||
*err_msg = alloc(s);
|
|
||||||
}
|
|
||||||
err.extended_code
|
|
||||||
}
|
|
||||||
err => {
|
|
||||||
*err_msg = alloc(&err.to_string());
|
|
||||||
ffi::SQLITE_ERROR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1271
vendor/rusqlite/src/functions.rs
vendored
1271
vendor/rusqlite/src/functions.rs
vendored
File diff suppressed because it is too large
Load Diff
1002
vendor/rusqlite/src/hooks/mod.rs
vendored
1002
vendor/rusqlite/src/hooks/mod.rs
vendored
File diff suppressed because it is too large
Load Diff
362
vendor/rusqlite/src/hooks/preupdate_hook.rs
vendored
362
vendor/rusqlite/src/hooks/preupdate_hook.rs
vendored
@@ -1,362 +0,0 @@
|
|||||||
use std::ffi::{c_char, c_int, c_void};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use super::expect_utf8;
|
|
||||||
use super::Action;
|
|
||||||
use crate::error::check;
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::inner_connection::InnerConnection;
|
|
||||||
use crate::types::ValueRef;
|
|
||||||
use crate::Connection;
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
/// The possible cases for when a PreUpdateHook gets triggered. Allows access to the relevant
|
|
||||||
/// functions for each case through the contained values.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PreUpdateCase {
|
|
||||||
/// Pre-update hook was triggered by an insert.
|
|
||||||
Insert(PreUpdateNewValueAccessor),
|
|
||||||
/// Pre-update hook was triggered by a delete.
|
|
||||||
Delete(PreUpdateOldValueAccessor),
|
|
||||||
/// Pre-update hook was triggered by an update.
|
|
||||||
Update {
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
old_value_accessor: PreUpdateOldValueAccessor,
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
new_value_accessor: PreUpdateNewValueAccessor,
|
|
||||||
},
|
|
||||||
/// This variant is not normally produced by SQLite. You may encounter it
|
|
||||||
/// if you're using a different version than what's supported by this library.
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PreUpdateCase> for Action {
|
|
||||||
fn from(puc: PreUpdateCase) -> Action {
|
|
||||||
match puc {
|
|
||||||
PreUpdateCase::Insert(_) => Action::SQLITE_INSERT,
|
|
||||||
PreUpdateCase::Delete(_) => Action::SQLITE_DELETE,
|
|
||||||
PreUpdateCase::Update { .. } => Action::SQLITE_UPDATE,
|
|
||||||
PreUpdateCase::Unknown => Action::UNKNOWN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An accessor to access the old values of the row being deleted/updated during the preupdate callback.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PreUpdateOldValueAccessor {
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
old_row_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PreUpdateOldValueAccessor {
|
|
||||||
/// Get the amount of columns in the row being deleted/updated.
|
|
||||||
pub fn get_column_count(&self) -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_preupdate_count(self.db) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the depth of the query that triggered the preupdate hook.
|
|
||||||
/// Returns 0 if the preupdate callback was invoked as a result of
|
|
||||||
/// a direct insert, update, or delete operation;
|
|
||||||
/// 1 for inserts, updates, or deletes invoked by top-level triggers;
|
|
||||||
/// 2 for changes resulting from triggers called by top-level triggers; and so forth.
|
|
||||||
pub fn get_query_depth(&self) -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_preupdate_depth(self.db) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the row id of the row being updated/deleted.
|
|
||||||
pub fn get_old_row_id(&self) -> i64 {
|
|
||||||
self.old_row_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of the row being updated/deleted at the specified index.
|
|
||||||
pub fn get_old_column_value(&self, i: i32) -> Result<ValueRef<'_>> {
|
|
||||||
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
|
|
||||||
unsafe {
|
|
||||||
check(ffi::sqlite3_preupdate_old(self.db, i, &mut p_value))?;
|
|
||||||
Ok(ValueRef::from_value(p_value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An accessor to access the new values of the row being inserted/updated
|
|
||||||
/// during the preupdate callback.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PreUpdateNewValueAccessor {
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
new_row_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PreUpdateNewValueAccessor {
|
|
||||||
/// Get the amount of columns in the row being inserted/updated.
|
|
||||||
pub fn get_column_count(&self) -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_preupdate_count(self.db) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the depth of the query that triggered the preupdate hook.
|
|
||||||
/// Returns 0 if the preupdate callback was invoked as a result of
|
|
||||||
/// a direct insert, update, or delete operation;
|
|
||||||
/// 1 for inserts, updates, or deletes invoked by top-level triggers;
|
|
||||||
/// 2 for changes resulting from triggers called by top-level triggers; and so forth.
|
|
||||||
pub fn get_query_depth(&self) -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_preupdate_depth(self.db) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the row id of the row being inserted/updated.
|
|
||||||
pub fn get_new_row_id(&self) -> i64 {
|
|
||||||
self.new_row_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of the row being updated/deleted at the specified index.
|
|
||||||
pub fn get_new_column_value(&self, i: i32) -> Result<ValueRef<'_>> {
|
|
||||||
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
|
|
||||||
unsafe {
|
|
||||||
check(ffi::sqlite3_preupdate_new(self.db, i, &mut p_value))?;
|
|
||||||
Ok(ValueRef::from_value(p_value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Register a callback function to be invoked before
|
|
||||||
/// a row is updated, inserted or deleted.
|
|
||||||
///
|
|
||||||
/// The callback parameters are:
|
|
||||||
///
|
|
||||||
/// - the name of the database ("main", "temp", ...),
|
|
||||||
/// - the name of the table that is updated,
|
|
||||||
/// - a variant of the PreUpdateCase enum which allows access to extra functions depending
|
|
||||||
/// on whether it's an update, delete or insert.
|
|
||||||
#[inline]
|
|
||||||
pub fn preupdate_hook<F>(&self, hook: Option<F>) -> Result<()>
|
|
||||||
where
|
|
||||||
F: FnMut(Action, &str, &str, &PreUpdateCase) + Send + 'static,
|
|
||||||
{
|
|
||||||
self.db.borrow().check_owned()?;
|
|
||||||
self.db.borrow_mut().preupdate_hook(hook);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerConnection {
|
|
||||||
#[inline]
|
|
||||||
pub fn remove_preupdate_hook(&mut self) {
|
|
||||||
self.preupdate_hook(None::<fn(Action, &str, &str, &PreUpdateCase)>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```compile_fail
|
|
||||||
/// use rusqlite::{Connection, Result, hooks::PreUpdateCase};
|
|
||||||
/// fn main() -> Result<()> {
|
|
||||||
/// let db = Connection::open_in_memory()?;
|
|
||||||
/// {
|
|
||||||
/// let mut called = std::sync::atomic::AtomicBool::new(false);
|
|
||||||
/// db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| {
|
|
||||||
/// called.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
/// }));
|
|
||||||
/// }
|
|
||||||
/// db.execute_batch("CREATE TABLE foo AS SELECT 1 AS bar;")
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn preupdate_hook<F>(&mut self, hook: Option<F>)
|
|
||||||
where
|
|
||||||
F: FnMut(Action, &str, &str, &PreUpdateCase) + Send + 'static,
|
|
||||||
{
|
|
||||||
unsafe extern "C" fn call_boxed_closure<F>(
|
|
||||||
p_arg: *mut c_void,
|
|
||||||
sqlite: *mut ffi::sqlite3,
|
|
||||||
action_code: c_int,
|
|
||||||
db_name: *const c_char,
|
|
||||||
tbl_name: *const c_char,
|
|
||||||
old_row_id: i64,
|
|
||||||
new_row_id: i64,
|
|
||||||
) where
|
|
||||||
F: FnMut(Action, &str, &str, &PreUpdateCase),
|
|
||||||
{
|
|
||||||
let action = Action::from(action_code);
|
|
||||||
|
|
||||||
let preupdate_case = match action {
|
|
||||||
Action::SQLITE_INSERT => PreUpdateCase::Insert(PreUpdateNewValueAccessor {
|
|
||||||
db: sqlite,
|
|
||||||
new_row_id,
|
|
||||||
}),
|
|
||||||
Action::SQLITE_DELETE => PreUpdateCase::Delete(PreUpdateOldValueAccessor {
|
|
||||||
db: sqlite,
|
|
||||||
old_row_id,
|
|
||||||
}),
|
|
||||||
Action::SQLITE_UPDATE => PreUpdateCase::Update {
|
|
||||||
old_value_accessor: PreUpdateOldValueAccessor {
|
|
||||||
db: sqlite,
|
|
||||||
old_row_id,
|
|
||||||
},
|
|
||||||
new_value_accessor: PreUpdateNewValueAccessor {
|
|
||||||
db: sqlite,
|
|
||||||
new_row_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action::UNKNOWN => PreUpdateCase::Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
drop(catch_unwind(|| {
|
|
||||||
let boxed_hook: *mut F = p_arg.cast::<F>();
|
|
||||||
(*boxed_hook)(
|
|
||||||
action,
|
|
||||||
expect_utf8(db_name, "database name"),
|
|
||||||
expect_utf8(tbl_name, "table name"),
|
|
||||||
&preupdate_case,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let boxed_hook = hook.map(Box::new);
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_preupdate_hook(
|
|
||||||
self.db(),
|
|
||||||
boxed_hook.as_ref().map(|_| call_boxed_closure::<F> as _),
|
|
||||||
boxed_hook
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(ptr::null_mut, |h| &**h as *const F as *mut _),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
self.preupdate_hook = boxed_hook.map(|bh| bh as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use super::super::Action;
|
|
||||||
use super::PreUpdateCase;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_preupdate_hook_insert() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| {
|
|
||||||
assert_eq!(Action::SQLITE_INSERT, action);
|
|
||||||
assert_eq!("main", db);
|
|
||||||
assert_eq!("foo", tbl);
|
|
||||||
match case {
|
|
||||||
PreUpdateCase::Insert(accessor) => {
|
|
||||||
assert_eq!(1, accessor.get_column_count());
|
|
||||||
assert_eq!(1, accessor.get_new_row_id());
|
|
||||||
assert_eq!(0, accessor.get_query_depth());
|
|
||||||
// out of bounds access should return an error
|
|
||||||
assert!(accessor.get_new_column_value(1).is_err());
|
|
||||||
assert_eq!(
|
|
||||||
"lisa",
|
|
||||||
accessor.get_new_column_value(0).unwrap().as_str().unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(0, accessor.get_query_depth());
|
|
||||||
}
|
|
||||||
_ => panic!("wrong preupdate case"),
|
|
||||||
}
|
|
||||||
CALLED.store(true, Ordering::Relaxed);
|
|
||||||
}))?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT)")?;
|
|
||||||
db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
|
|
||||||
assert!(CALLED.load(Ordering::Relaxed));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_preupdate_hook_delete() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT)")?;
|
|
||||||
db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
|
|
||||||
|
|
||||||
db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| {
|
|
||||||
assert_eq!(Action::SQLITE_DELETE, action);
|
|
||||||
assert_eq!("main", db);
|
|
||||||
assert_eq!("foo", tbl);
|
|
||||||
match case {
|
|
||||||
PreUpdateCase::Delete(accessor) => {
|
|
||||||
assert_eq!(1, accessor.get_column_count());
|
|
||||||
assert_eq!(1, accessor.get_old_row_id());
|
|
||||||
assert_eq!(0, accessor.get_query_depth());
|
|
||||||
// out of bounds access should return an error
|
|
||||||
assert!(accessor.get_old_column_value(1).is_err());
|
|
||||||
assert_eq!(
|
|
||||||
"lisa",
|
|
||||||
accessor.get_old_column_value(0).unwrap().as_str().unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(0, accessor.get_query_depth());
|
|
||||||
}
|
|
||||||
_ => panic!("wrong preupdate case"),
|
|
||||||
}
|
|
||||||
CALLED.store(true, Ordering::Relaxed);
|
|
||||||
}))?;
|
|
||||||
|
|
||||||
db.execute_batch("DELETE from foo")?;
|
|
||||||
assert!(CALLED.load(Ordering::Relaxed));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_preupdate_hook_update() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT)")?;
|
|
||||||
db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
|
|
||||||
|
|
||||||
db.preupdate_hook(Some(|action, db: &str, tbl: &str, case: &PreUpdateCase| {
|
|
||||||
assert_eq!(Action::SQLITE_UPDATE, action);
|
|
||||||
assert_eq!("main", db);
|
|
||||||
assert_eq!("foo", tbl);
|
|
||||||
match case {
|
|
||||||
PreUpdateCase::Update {
|
|
||||||
old_value_accessor,
|
|
||||||
new_value_accessor,
|
|
||||||
} => {
|
|
||||||
assert_eq!(1, old_value_accessor.get_column_count());
|
|
||||||
assert_eq!(1, old_value_accessor.get_old_row_id());
|
|
||||||
assert_eq!(0, old_value_accessor.get_query_depth());
|
|
||||||
// out of bounds access should return an error
|
|
||||||
assert!(old_value_accessor.get_old_column_value(1).is_err());
|
|
||||||
assert_eq!(
|
|
||||||
"lisa",
|
|
||||||
old_value_accessor
|
|
||||||
.get_old_column_value(0)
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(0, old_value_accessor.get_query_depth());
|
|
||||||
|
|
||||||
assert_eq!(1, new_value_accessor.get_column_count());
|
|
||||||
assert_eq!(1, new_value_accessor.get_new_row_id());
|
|
||||||
assert_eq!(0, new_value_accessor.get_query_depth());
|
|
||||||
// out of bounds access should return an error
|
|
||||||
assert!(new_value_accessor.get_new_column_value(1).is_err());
|
|
||||||
assert_eq!(
|
|
||||||
"janice",
|
|
||||||
new_value_accessor
|
|
||||||
.get_new_column_value(0)
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(0, new_value_accessor.get_query_depth());
|
|
||||||
}
|
|
||||||
_ => panic!("wrong preupdate case"),
|
|
||||||
}
|
|
||||||
CALLED.store(true, Ordering::Relaxed);
|
|
||||||
}))?;
|
|
||||||
|
|
||||||
db.execute_batch("UPDATE foo SET t = 'janice'")?;
|
|
||||||
assert!(CALLED.load(Ordering::Relaxed));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
446
vendor/rusqlite/src/inner_connection.rs
vendored
446
vendor/rusqlite/src/inner_connection.rs
vendored
@@ -1,446 +0,0 @@
|
|||||||
use std::ffi::{c_char, c_int, CStr};
|
|
||||||
#[cfg(feature = "load_extension")]
|
|
||||||
use std::path::Path;
|
|
||||||
use std::ptr;
|
|
||||||
use std::str;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use super::ffi;
|
|
||||||
use super::{Connection, InterruptHandle, Name, OpenFlags, PrepFlags, Result};
|
|
||||||
use crate::error::{decode_result_raw, error_from_handle, error_with_offset, Error};
|
|
||||||
use crate::raw_statement::RawStatement;
|
|
||||||
use crate::statement::Statement;
|
|
||||||
use crate::version_number;
|
|
||||||
|
|
||||||
pub struct InnerConnection {
|
|
||||||
pub db: *mut ffi::sqlite3,
|
|
||||||
// It's unsafe to call `sqlite3_close` while another thread is performing
|
|
||||||
// a `sqlite3_interrupt`, and vice versa, so we take this mutex during
|
|
||||||
// those functions. This protects a copy of the `db` pointer (which is
|
|
||||||
// cleared on closing), however the main copy, `db`, is unprotected.
|
|
||||||
// Otherwise, a long-running query would prevent calling interrupt, as
|
|
||||||
// interrupt would only acquire the lock after the query's completion.
|
|
||||||
interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
pub commit_hook: Option<Box<dyn FnMut() -> bool + Send>>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
pub rollback_hook: Option<Box<dyn FnMut() + Send>>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
#[expect(clippy::type_complexity)]
|
|
||||||
pub update_hook: Option<Box<dyn FnMut(crate::hooks::Action, &str, &str, i64) + Send>>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
|
|
||||||
#[cfg(feature = "preupdate_hook")]
|
|
||||||
#[expect(clippy::type_complexity)]
|
|
||||||
pub preupdate_hook: Option<
|
|
||||||
Box<dyn FnMut(crate::hooks::Action, &str, &str, &crate::hooks::PreUpdateCase) + Send>,
|
|
||||||
>,
|
|
||||||
owned: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for InnerConnection {}
|
|
||||||
|
|
||||||
impl InnerConnection {
|
|
||||||
#[expect(clippy::arc_with_non_send_sync)] // See unsafe impl Send / Sync for InterruptHandle
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
db,
|
|
||||||
interrupt_lock: Arc::new(Mutex::new(if owned { db } else { ptr::null_mut() })),
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
commit_hook: None,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
rollback_hook: None,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
update_hook: None,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
progress_handler: None,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
authorizer: None,
|
|
||||||
#[cfg(feature = "preupdate_hook")]
|
|
||||||
preupdate_hook: None,
|
|
||||||
owned,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_with_flags(
|
|
||||||
c_path: &CStr,
|
|
||||||
mut flags: OpenFlags,
|
|
||||||
vfs: Option<&CStr>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
ensure_safe_sqlite_threading_mode()?;
|
|
||||||
|
|
||||||
let z_vfs = match vfs {
|
|
||||||
Some(c_vfs) => c_vfs.as_ptr(),
|
|
||||||
None => ptr::null(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// turn on extended results code before opening database to have a better diagnostic if a failure happens
|
|
||||||
let exrescode = if version_number() >= 3_037_000 {
|
|
||||||
flags |= OpenFlags::SQLITE_OPEN_EXRESCODE;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false // flag SQLITE_OPEN_EXRESCODE is ignored by SQLite version < 3.37.0
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let mut db: *mut ffi::sqlite3 = ptr::null_mut();
|
|
||||||
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs);
|
|
||||||
if r != ffi::SQLITE_OK {
|
|
||||||
let e = if db.is_null() {
|
|
||||||
err!(r, "{}", c_path.to_string_lossy())
|
|
||||||
} else {
|
|
||||||
let mut e = error_from_handle(db, r);
|
|
||||||
if let Error::SqliteFailure(
|
|
||||||
ffi::Error {
|
|
||||||
code: ffi::ErrorCode::CannotOpen,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
Some(msg),
|
|
||||||
) = e
|
|
||||||
{
|
|
||||||
e = err!(r, "{msg}: {}", c_path.to_string_lossy());
|
|
||||||
}
|
|
||||||
ffi::sqlite3_close(db);
|
|
||||||
e
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt to turn on extended results code; don't fail if we can't.
|
|
||||||
if !exrescode {
|
|
||||||
ffi::sqlite3_extended_result_codes(db, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = ffi::sqlite3_busy_timeout(db, 5000);
|
|
||||||
if r != ffi::SQLITE_OK {
|
|
||||||
let e = error_from_handle(db, r);
|
|
||||||
ffi::sqlite3_close(db);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self::new(db, true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn db(&self) -> *mut ffi::sqlite3 {
|
|
||||||
self.db
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn decode_result(&self, code: c_int) -> Result<()> {
|
|
||||||
unsafe { decode_result_raw(self.db(), code) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close(&mut self) -> Result<()> {
|
|
||||||
if self.db.is_null() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if self.owned {
|
|
||||||
self.remove_hooks();
|
|
||||||
self.remove_preupdate_hook();
|
|
||||||
}
|
|
||||||
let mut shared_handle = self.interrupt_lock.lock().unwrap();
|
|
||||||
assert!(
|
|
||||||
!self.owned || !shared_handle.is_null(),
|
|
||||||
"Bug: Somehow interrupt_lock was cleared before the DB was closed"
|
|
||||||
);
|
|
||||||
if !self.owned {
|
|
||||||
self.db = ptr::null_mut();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
let r = ffi::sqlite3_close(self.db);
|
|
||||||
// Need to use _raw because _guard has a reference out, and
|
|
||||||
// decode_result takes &mut self.
|
|
||||||
let r = decode_result_raw(self.db, r);
|
|
||||||
if r.is_ok() {
|
|
||||||
*shared_handle = ptr::null_mut();
|
|
||||||
self.db = ptr::null_mut();
|
|
||||||
}
|
|
||||||
r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_interrupt_handle(&self) -> InterruptHandle {
|
|
||||||
InterruptHandle {
|
|
||||||
db_lock: Arc::clone(&self.interrupt_lock),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "load_extension")]
|
|
||||||
pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
|
|
||||||
let r = ffi::sqlite3_enable_load_extension(self.db, onoff);
|
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "load_extension")]
|
|
||||||
pub unsafe fn load_extension<N: Name>(
|
|
||||||
&self,
|
|
||||||
dylib_path: &Path,
|
|
||||||
entry_point: Option<N>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let dylib_str = super::path_to_cstring(dylib_path)?;
|
|
||||||
let mut errmsg: *mut c_char = ptr::null_mut();
|
|
||||||
let cs = entry_point.as_ref().map(N::as_cstr).transpose()?;
|
|
||||||
let c_entry = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
|
||||||
let r = ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry, &mut errmsg);
|
|
||||||
if r == ffi::SQLITE_OK {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
let message = super::errmsg_to_string(errmsg);
|
|
||||||
ffi::sqlite3_free(errmsg.cast::<std::ffi::c_void>());
|
|
||||||
Err(crate::error::error_from_sqlite_code(r, Some(message)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn last_insert_rowid(&self) -> i64 {
|
|
||||||
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prepare<'a>(
|
|
||||||
&mut self,
|
|
||||||
conn: &'a Connection,
|
|
||||||
sql: &str,
|
|
||||||
flags: PrepFlags,
|
|
||||||
) -> Result<(Statement<'a>, usize)> {
|
|
||||||
let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
|
|
||||||
let Ok(len) = c_int::try_from(sql.len()) else {
|
|
||||||
return Err(err!(ffi::SQLITE_TOOBIG));
|
|
||||||
};
|
|
||||||
let c_sql = sql.as_bytes().as_ptr().cast::<c_char>();
|
|
||||||
let mut c_tail: *const c_char = ptr::null();
|
|
||||||
#[cfg(not(feature = "unlock_notify"))]
|
|
||||||
let r = unsafe {
|
|
||||||
ffi::sqlite3_prepare_v3(
|
|
||||||
self.db(),
|
|
||||||
c_sql,
|
|
||||||
len,
|
|
||||||
flags.bits(),
|
|
||||||
&mut c_stmt,
|
|
||||||
&mut c_tail,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
#[cfg(feature = "unlock_notify")]
|
|
||||||
let r = unsafe {
|
|
||||||
use crate::unlock_notify;
|
|
||||||
let mut rc;
|
|
||||||
loop {
|
|
||||||
rc = ffi::sqlite3_prepare_v3(
|
|
||||||
self.db(),
|
|
||||||
c_sql,
|
|
||||||
len,
|
|
||||||
flags.bits(),
|
|
||||||
&mut c_stmt,
|
|
||||||
&mut c_tail,
|
|
||||||
);
|
|
||||||
if !unlock_notify::is_locked(self.db, rc) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rc = unlock_notify::wait_for_unlock_notify(self.db);
|
|
||||||
if rc != ffi::SQLITE_OK {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rc
|
|
||||||
};
|
|
||||||
// If there is an error, *ppStmt is set to NULL.
|
|
||||||
if r != ffi::SQLITE_OK {
|
|
||||||
return Err(unsafe { error_with_offset(self.db, r, sql) });
|
|
||||||
}
|
|
||||||
// If the input text contains no SQL (if the input is an empty string or a
|
|
||||||
// comment) then *ppStmt is set to NULL.
|
|
||||||
let tail = if c_tail.is_null() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let n = (c_tail as isize) - (c_sql as isize);
|
|
||||||
if n <= 0 || n >= len as isize {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
n as usize
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok((
|
|
||||||
Statement::new(conn, unsafe { RawStatement::new(c_stmt) }),
|
|
||||||
tail,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn changes(&self) -> u64 {
|
|
||||||
#[cfg(not(feature = "modern_sqlite"))]
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_changes(self.db()) as u64
|
|
||||||
}
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_changes64(self.db()) as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn total_changes(&self) -> u64 {
|
|
||||||
#[cfg(not(feature = "modern_sqlite"))]
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_total_changes(self.db()) as u64
|
|
||||||
}
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_total_changes64(self.db()) as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_autocommit(&self) -> bool {
|
|
||||||
unsafe { get_autocommit(self.db()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_busy(&self) -> bool {
|
|
||||||
let db = self.db();
|
|
||||||
unsafe {
|
|
||||||
let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut());
|
|
||||||
while !stmt.is_null() {
|
|
||||||
if ffi::sqlite3_stmt_busy(stmt) != 0 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
stmt = ffi::sqlite3_next_stmt(db, stmt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cache_flush(&mut self) -> Result<()> {
|
|
||||||
crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "hooks"))]
|
|
||||||
#[inline]
|
|
||||||
fn remove_hooks(&mut self) {}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "preupdate_hook"))]
|
|
||||||
#[inline]
|
|
||||||
fn remove_preupdate_hook(&mut self) {}
|
|
||||||
|
|
||||||
pub fn db_readonly<N: Name>(&self, db_name: N) -> Result<bool> {
|
|
||||||
let name = db_name.as_cstr()?;
|
|
||||||
let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) };
|
|
||||||
match r {
|
|
||||||
0 => Ok(false),
|
|
||||||
1 => Ok(true),
|
|
||||||
-1 => Err(err!(
|
|
||||||
ffi::SQLITE_MISUSE,
|
|
||||||
"{db_name:?} is not the name of a database"
|
|
||||||
)),
|
|
||||||
_ => Err(err!(r, "Unexpected result")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
|
||||||
pub fn txn_state<N: Name>(
|
|
||||||
&self,
|
|
||||||
db_name: Option<N>,
|
|
||||||
) -> Result<super::transaction::TransactionState> {
|
|
||||||
let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
|
|
||||||
let name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
|
||||||
let r = unsafe { ffi::sqlite3_txn_state(self.db, name) };
|
|
||||||
match r {
|
|
||||||
0 => Ok(super::transaction::TransactionState::None),
|
|
||||||
1 => Ok(super::transaction::TransactionState::Read),
|
|
||||||
2 => Ok(super::transaction::TransactionState::Write),
|
|
||||||
-1 => Err(err!(
|
|
||||||
ffi::SQLITE_MISUSE,
|
|
||||||
"{db_name:?} is not the name of a valid schema"
|
|
||||||
)),
|
|
||||||
_ => Err(err!(r, "Unexpected result")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn release_memory(&self) -> Result<()> {
|
|
||||||
self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.41.0
|
|
||||||
pub fn is_interrupted(&self) -> bool {
|
|
||||||
unsafe { ffi::sqlite3_is_interrupted(self.db) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "hooks", feature = "preupdate_hook"))]
|
|
||||||
pub fn check_owned(&self) -> Result<()> {
|
|
||||||
if !self.owned {
|
|
||||||
return Err(err!(ffi::SQLITE_MISUSE, "Connection is not owned"));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn get_autocommit(ptr: *mut ffi::sqlite3) -> bool {
|
|
||||||
ffi::sqlite3_get_autocommit(ptr) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn db_filename<N: Name>(
|
|
||||||
_: std::marker::PhantomData<&()>,
|
|
||||||
ptr: *mut ffi::sqlite3,
|
|
||||||
db_name: N,
|
|
||||||
) -> Option<&str> {
|
|
||||||
let db_name = db_name.as_cstr().unwrap();
|
|
||||||
let db_filename = ffi::sqlite3_db_filename(ptr, db_name.as_ptr());
|
|
||||||
if db_filename.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
CStr::from_ptr(db_filename).to_str().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for InnerConnection {
|
|
||||||
#[expect(unused_must_use)]
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// threading mode checks are not necessary (and do not work) on target
|
|
||||||
// platforms that do not have threading (such as webassembly)
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_arch = "wasm32")))]
|
|
||||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
|
||||||
// Ensure SQLite was compiled in threadsafe mode.
|
|
||||||
if unsafe { ffi::sqlite3_threadsafe() == 0 } {
|
|
||||||
return Err(Error::SqliteSingleThreadedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode,
|
|
||||||
// but it's possible someone configured it to be in Single-thread mode
|
|
||||||
// before calling into us. That would mean we're exposing an unsafe API via
|
|
||||||
// a safe one (in Rust terminology).
|
|
||||||
//
|
|
||||||
// We can ask SQLite for a mutex and check for
|
|
||||||
// the magic value 8. This isn't documented, but it's what SQLite
|
|
||||||
// returns for its mutex allocation function in Single-thread mode.
|
|
||||||
const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
|
|
||||||
let is_singlethreaded = unsafe {
|
|
||||||
let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
|
|
||||||
let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC;
|
|
||||||
ffi::sqlite3_mutex_free(mutex_ptr);
|
|
||||||
is_singlethreaded
|
|
||||||
};
|
|
||||||
if is_singlethreaded {
|
|
||||||
Err(Error::SqliteSingleThreadedMode)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2330
vendor/rusqlite/src/lib.rs
vendored
2330
vendor/rusqlite/src/lib.rs
vendored
File diff suppressed because it is too large
Load Diff
182
vendor/rusqlite/src/limits.rs
vendored
182
vendor/rusqlite/src/limits.rs
vendored
@@ -1,182 +0,0 @@
|
|||||||
//! Run-Time Limits
|
|
||||||
|
|
||||||
use crate::{ffi, Connection, Result};
|
|
||||||
use std::ffi::c_int;
|
|
||||||
|
|
||||||
/// Run-Time limit categories, for use with [`Connection::limit`] and
|
|
||||||
/// [`Connection::set_limit`].
|
|
||||||
///
|
|
||||||
/// See the official documentation for more information:
|
|
||||||
/// - <https://www.sqlite.org/c3ref/c_limit_attached.html>
|
|
||||||
/// - <https://www.sqlite.org/limits.html>
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
#[repr(i32)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[expect(non_camel_case_types)]
|
|
||||||
pub enum Limit {
|
|
||||||
/// The maximum size of any string or BLOB or table row, in bytes.
|
|
||||||
SQLITE_LIMIT_LENGTH = ffi::SQLITE_LIMIT_LENGTH,
|
|
||||||
/// The maximum length of an SQL statement, in bytes.
|
|
||||||
SQLITE_LIMIT_SQL_LENGTH = ffi::SQLITE_LIMIT_SQL_LENGTH,
|
|
||||||
/// The maximum number of columns in a table definition or in the result set
|
|
||||||
/// of a SELECT or the maximum number of columns in an index or in an
|
|
||||||
/// ORDER BY or GROUP BY clause.
|
|
||||||
SQLITE_LIMIT_COLUMN = ffi::SQLITE_LIMIT_COLUMN,
|
|
||||||
/// The maximum depth of the parse tree on any expression.
|
|
||||||
SQLITE_LIMIT_EXPR_DEPTH = ffi::SQLITE_LIMIT_EXPR_DEPTH,
|
|
||||||
/// The maximum number of terms in a compound SELECT statement.
|
|
||||||
SQLITE_LIMIT_COMPOUND_SELECT = ffi::SQLITE_LIMIT_COMPOUND_SELECT,
|
|
||||||
/// The maximum number of instructions in a virtual machine program used to
|
|
||||||
/// implement an SQL statement.
|
|
||||||
SQLITE_LIMIT_VDBE_OP = ffi::SQLITE_LIMIT_VDBE_OP,
|
|
||||||
/// The maximum number of arguments on a function.
|
|
||||||
SQLITE_LIMIT_FUNCTION_ARG = ffi::SQLITE_LIMIT_FUNCTION_ARG,
|
|
||||||
/// The maximum number of attached databases.
|
|
||||||
SQLITE_LIMIT_ATTACHED = ffi::SQLITE_LIMIT_ATTACHED,
|
|
||||||
/// The maximum length of the pattern argument to the LIKE or GLOB
|
|
||||||
/// operators.
|
|
||||||
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
|
|
||||||
/// The maximum index number of any parameter in an SQL statement.
|
|
||||||
SQLITE_LIMIT_VARIABLE_NUMBER = ffi::SQLITE_LIMIT_VARIABLE_NUMBER,
|
|
||||||
/// The maximum depth of recursion for triggers.
|
|
||||||
SQLITE_LIMIT_TRIGGER_DEPTH = ffi::SQLITE_LIMIT_TRIGGER_DEPTH,
|
|
||||||
/// The maximum number of auxiliary worker threads that a single prepared
|
|
||||||
/// statement may start.
|
|
||||||
SQLITE_LIMIT_WORKER_THREADS = ffi::SQLITE_LIMIT_WORKER_THREADS,
|
|
||||||
/// Only used for testing
|
|
||||||
#[cfg(test)]
|
|
||||||
INVALID = -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Returns the current value of a [`Limit`].
|
|
||||||
#[inline]
|
|
||||||
pub fn limit(&self, limit: Limit) -> Result<i32> {
|
|
||||||
let c = self.db.borrow();
|
|
||||||
let rc = unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, -1) };
|
|
||||||
if rc < 0 {
|
|
||||||
return Err(err!(ffi::SQLITE_RANGE, "{limit:?} is invalid"));
|
|
||||||
}
|
|
||||||
Ok(rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Changes the [`Limit`] to `new_val`, returning the prior
|
|
||||||
/// value of the limit.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_limit(&self, limit: Limit, new_val: i32) -> Result<i32> {
|
|
||||||
if new_val < 0 {
|
|
||||||
return Err(err!(ffi::SQLITE_RANGE, "{new_val} is invalid"));
|
|
||||||
}
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
let rc = unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, new_val) };
|
|
||||||
if rc < 0 {
|
|
||||||
return Err(err!(ffi::SQLITE_RANGE, "{limit:?} is invalid"));
|
|
||||||
}
|
|
||||||
Ok(rc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_limit_values() {
|
|
||||||
assert_eq!(Limit::SQLITE_LIMIT_LENGTH as i32, ffi::SQLITE_LIMIT_LENGTH,);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_SQL_LENGTH as i32,
|
|
||||||
ffi::SQLITE_LIMIT_SQL_LENGTH,
|
|
||||||
);
|
|
||||||
assert_eq!(Limit::SQLITE_LIMIT_COLUMN as i32, ffi::SQLITE_LIMIT_COLUMN,);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_EXPR_DEPTH as i32,
|
|
||||||
ffi::SQLITE_LIMIT_EXPR_DEPTH,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_COMPOUND_SELECT as i32,
|
|
||||||
ffi::SQLITE_LIMIT_COMPOUND_SELECT,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_VDBE_OP as i32,
|
|
||||||
ffi::SQLITE_LIMIT_VDBE_OP,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_FUNCTION_ARG as i32,
|
|
||||||
ffi::SQLITE_LIMIT_FUNCTION_ARG,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_ATTACHED as i32,
|
|
||||||
ffi::SQLITE_LIMIT_ATTACHED,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
|
|
||||||
ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
|
|
||||||
ffi::SQLITE_LIMIT_VARIABLE_NUMBER,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
|
|
||||||
ffi::SQLITE_LIMIT_TRIGGER_DEPTH,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Limit::SQLITE_LIMIT_WORKER_THREADS as i32,
|
|
||||||
ffi::SQLITE_LIMIT_WORKER_THREADS,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_limit() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_LENGTH, 1024)?;
|
|
||||||
assert_eq!(1024, db.limit(Limit::SQLITE_LIMIT_LENGTH)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_SQL_LENGTH, 1024)?;
|
|
||||||
assert_eq!(1024, db.limit(Limit::SQLITE_LIMIT_SQL_LENGTH)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_COLUMN, 64)?;
|
|
||||||
assert_eq!(64, db.limit(Limit::SQLITE_LIMIT_COLUMN)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_EXPR_DEPTH, 256)?;
|
|
||||||
assert_eq!(256, db.limit(Limit::SQLITE_LIMIT_EXPR_DEPTH)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_COMPOUND_SELECT, 32)?;
|
|
||||||
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_COMPOUND_SELECT)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_FUNCTION_ARG, 32)?;
|
|
||||||
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_FUNCTION_ARG)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_ATTACHED, 2)?;
|
|
||||||
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_ATTACHED)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH, 128)?;
|
|
||||||
assert_eq!(128, db.limit(Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, 99)?;
|
|
||||||
assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32)?;
|
|
||||||
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH)?);
|
|
||||||
|
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2)?;
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS)?);
|
|
||||||
|
|
||||||
// wasm build with DSQLITE_THREADSAFE=0, so limit not working
|
|
||||||
// see <https://sqlite.org/threadsafe.html>
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
assert_eq!(0, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS)?);
|
|
||||||
|
|
||||||
assert!(db
|
|
||||||
.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, -1)
|
|
||||||
.is_err());
|
|
||||||
assert!(db.set_limit(Limit::INVALID, 0).is_err());
|
|
||||||
assert!(db.limit(Limit::INVALID).is_err());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
vendor/rusqlite/src/load_extension_guard.rs
vendored
45
vendor/rusqlite/src/load_extension_guard.rs
vendored
@@ -1,45 +0,0 @@
|
|||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
/// RAII guard temporarily enabling SQLite extensions to be loaded.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
|
|
||||||
/// # use std::path::{Path};
|
|
||||||
/// fn load_my_extension(conn: &Connection) -> Result<()> {
|
|
||||||
/// unsafe {
|
|
||||||
/// let _guard = LoadExtensionGuard::new(conn)?;
|
|
||||||
/// conn.load_extension("trusted/sqlite/extension", None::<&str>)
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub struct LoadExtensionGuard<'conn> {
|
|
||||||
conn: &'conn Connection,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LoadExtensionGuard<'_> {
|
|
||||||
/// Attempt to enable loading extensions. Loading extensions will be
|
|
||||||
/// disabled when this guard goes out of scope. Cannot be meaningfully
|
|
||||||
/// nested.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must not run untrusted queries while extension loading is enabled.
|
|
||||||
///
|
|
||||||
/// See the safety comment on [`Connection::load_extension_enable`] for more
|
|
||||||
/// details.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
|
|
||||||
conn.load_extension_enable()
|
|
||||||
.map(|_| LoadExtensionGuard { conn })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(unused_must_use)]
|
|
||||||
impl Drop for LoadExtensionGuard<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.conn.load_extension_disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
453
vendor/rusqlite/src/params.rs
vendored
453
vendor/rusqlite/src/params.rs
vendored
@@ -1,453 +0,0 @@
|
|||||||
use crate::{BindIndex, Result, Statement, ToSql};
|
|
||||||
|
|
||||||
mod sealed {
|
|
||||||
/// This trait exists just to ensure that the only impls of `trait Params`
|
|
||||||
/// that are allowed are ones in this crate.
|
|
||||||
pub trait Sealed {}
|
|
||||||
}
|
|
||||||
use sealed::Sealed;
|
|
||||||
|
|
||||||
/// Trait used for [sets of parameter][params] passed into SQL
|
|
||||||
/// statements/queries.
|
|
||||||
///
|
|
||||||
/// [params]: https://www.sqlite.org/c3ref/bind_blob.html
|
|
||||||
///
|
|
||||||
/// Note: Currently, this trait can only be implemented inside this crate.
|
|
||||||
/// Additionally, it's methods (which are `doc(hidden)`) should currently not be
|
|
||||||
/// considered part of the stable API, although it's possible they will
|
|
||||||
/// stabilize in the future.
|
|
||||||
///
|
|
||||||
/// # Passing parameters to SQLite
|
|
||||||
///
|
|
||||||
/// Many functions in this library let you pass parameters to SQLite. Doing this
|
|
||||||
/// lets you avoid any risk of SQL injection, and is simpler than escaping
|
|
||||||
/// things manually. Aside from deprecated functions and a few helpers, this is
|
|
||||||
/// indicated by the function taking a generic argument that implements `Params`
|
|
||||||
/// (this trait).
|
|
||||||
///
|
|
||||||
/// ## Positional parameters
|
|
||||||
///
|
|
||||||
/// For cases where you want to pass a list of parameters where the number of
|
|
||||||
/// parameters is known at compile time, this can be done in one of the
|
|
||||||
/// following ways:
|
|
||||||
///
|
|
||||||
/// - For small lists of parameters up to 16 items, they may alternatively be
|
|
||||||
/// passed as a tuple, as in `thing.query((1, "foo"))`.
|
|
||||||
/// This is somewhat inconvenient for a single item, since you need a
|
|
||||||
/// weird-looking trailing comma: `thing.query(("example",))`. That case is
|
|
||||||
/// perhaps more cleanly expressed as `thing.query(["example"])`.
|
|
||||||
///
|
|
||||||
/// - Using the [`rusqlite::params!`](crate::params!) macro, e.g.
|
|
||||||
/// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for
|
|
||||||
/// heterogeneous lists where the number of parameters greater than 16, or
|
|
||||||
/// homogeneous lists of parameters where the number of parameters exceeds 32.
|
|
||||||
///
|
|
||||||
/// - For small homogeneous lists of parameters, they can either be passed as:
|
|
||||||
///
|
|
||||||
/// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo",
|
|
||||||
/// "bar", "baz"])`.
|
|
||||||
///
|
|
||||||
/// - a reference to an array of references, as in `thing.query(&["foo",
|
|
||||||
/// "bar", "baz"])` or `thing.query(&[&1i32, &2, &3])`.
|
|
||||||
/// (Note: in this case we don't implement this for slices for coherence
|
|
||||||
/// reasons, so it really is only for the "reference to array" types —
|
|
||||||
/// hence why the number of parameters must be <= 32, or you need to
|
|
||||||
/// reach for `rusqlite::params!`)
|
|
||||||
///
|
|
||||||
/// Unfortunately, in the current design it's not possible to allow this for
|
|
||||||
/// references to arrays of non-references (e.g. `&[1i32, 2, 3]`). Code like
|
|
||||||
/// this should instead either use `params!`, an array literal, a `&[&dyn
|
|
||||||
/// ToSql]` or if none of those work, [`ParamsFromIter`].
|
|
||||||
///
|
|
||||||
/// - As a slice of `ToSql` trait object references, e.g. `&[&dyn ToSql]`. This
|
|
||||||
/// is mostly useful for passing parameter lists around as arguments without
|
|
||||||
/// having every function take a generic `P: Params`.
|
|
||||||
///
|
|
||||||
/// ### Example (positional)
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result, params};
|
|
||||||
/// fn update_rows(conn: &Connection) -> Result<()> {
|
|
||||||
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)")?;
|
|
||||||
///
|
|
||||||
/// // Using a tuple:
|
|
||||||
/// stmt.execute((0, "foobar"))?;
|
|
||||||
///
|
|
||||||
/// // Using `rusqlite::params!`:
|
|
||||||
/// stmt.execute(params![1i32, "blah"])?;
|
|
||||||
///
|
|
||||||
/// // array literal — non-references
|
|
||||||
/// stmt.execute([2i32, 3i32])?;
|
|
||||||
///
|
|
||||||
/// // array literal — references
|
|
||||||
/// stmt.execute(["foo", "bar"])?;
|
|
||||||
///
|
|
||||||
/// // Slice literal, references:
|
|
||||||
/// stmt.execute(&[&2i32, &3i32])?;
|
|
||||||
///
|
|
||||||
/// // Note: The types behind the references don't have to be `Sized`
|
|
||||||
/// stmt.execute(&["foo", "bar"])?;
|
|
||||||
///
|
|
||||||
/// // However, this doesn't work (see above):
|
|
||||||
/// // stmt.execute(&[1i32, 2i32])?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Named parameters
|
|
||||||
///
|
|
||||||
/// SQLite lets you name parameters using a number of conventions (":foo",
|
|
||||||
/// "@foo", "$foo"). You can pass named parameters in to SQLite using rusqlite
|
|
||||||
/// in a few ways:
|
|
||||||
///
|
|
||||||
/// - Using the [`rusqlite::named_params!`](crate::named_params!) macro, as in
|
|
||||||
/// `stmt.execute(named_params!{ ":name": "foo", ":age": 99 })`. Similar to
|
|
||||||
/// the `params` macro, this is most useful for heterogeneous lists of
|
|
||||||
/// parameters, or lists where the number of parameters exceeds 32.
|
|
||||||
///
|
|
||||||
/// - As a slice of `&[(&str, &dyn ToSql)]`. This is what essentially all of
|
|
||||||
/// these boil down to in the end, conceptually at least. In theory, you can
|
|
||||||
/// pass this as `stmt`.
|
|
||||||
///
|
|
||||||
/// - As array references, similar to the positional params. This looks like
|
|
||||||
/// `thing.query(&[(":foo", &1i32), (":bar", &2i32)])` or
|
|
||||||
/// `thing.query(&[(":foo", "abc"), (":bar", "def")])`.
|
|
||||||
///
|
|
||||||
/// Note: Unbound named parameters will be left to the value they previously
|
|
||||||
/// were bound with, falling back to `NULL` for parameters which have never been
|
|
||||||
/// bound.
|
|
||||||
///
|
|
||||||
/// ### Example (named)
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result, named_params};
|
|
||||||
/// fn insert(conn: &Connection) -> Result<()> {
|
|
||||||
/// let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :val)")?;
|
|
||||||
/// // Using `rusqlite::params!`:
|
|
||||||
/// stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
|
|
||||||
/// // Alternatively:
|
|
||||||
/// stmt.execute(&[(":key", "three"), (":val", "four")])?;
|
|
||||||
/// // Or:
|
|
||||||
/// stmt.execute(&[(":key", &100), (":val", &200)])?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## No parameters
|
|
||||||
///
|
|
||||||
/// You can just use an empty tuple or the empty array literal to run a query
|
|
||||||
/// that accepts no parameters.
|
|
||||||
///
|
|
||||||
/// ### Example (no parameters)
|
|
||||||
///
|
|
||||||
/// The empty tuple:
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result, params};
|
|
||||||
/// fn delete_all_users(conn: &Connection) -> Result<()> {
|
|
||||||
/// // You may also use `()`.
|
|
||||||
/// conn.execute("DELETE FROM users", ())?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The empty array:
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result, params};
|
|
||||||
/// fn delete_all_users(conn: &Connection) -> Result<()> {
|
|
||||||
/// // Just use an empty array (e.g. `[]`) for no params.
|
|
||||||
/// conn.execute("DELETE FROM users", [])?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Dynamic parameter list
|
|
||||||
///
|
|
||||||
/// If you have a number of parameters which is unknown at compile time (for
|
|
||||||
/// example, building a dynamic query at runtime), you have two choices:
|
|
||||||
///
|
|
||||||
/// - Use a `&[&dyn ToSql]`. This is often annoying to construct if you don't
|
|
||||||
/// already have this type on-hand.
|
|
||||||
/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an
|
|
||||||
/// iterator some `T: ToSql` with something that implements `Params`. The
|
|
||||||
/// usage of this looks like `rusqlite::params_from_iter(something)`.
|
|
||||||
///
|
|
||||||
/// A lot of the considerations here are similar either way, so you should see
|
|
||||||
/// the [`ParamsFromIter`] documentation for more info / examples.
|
|
||||||
pub trait Params: Sealed {
|
|
||||||
// XXX not public api, might not need to expose.
|
|
||||||
//
|
|
||||||
// Binds the parameters to the statement. It is unlikely calling this
|
|
||||||
// explicitly will do what you want. Please use `Statement::query` or
|
|
||||||
// similar directly.
|
|
||||||
//
|
|
||||||
// For now, just hide the function in the docs...
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
|
|
||||||
// unambiguous, this must be the *only* implementation for an empty array.
|
|
||||||
//
|
|
||||||
// This sadly prevents `impl<T: ToSql, const N: usize> Params for [T; N]`, which
|
|
||||||
// forces people to use `params![...]` or `rusqlite::params_from_iter` for long
|
|
||||||
// homogeneous lists of parameters. This is not that big of a deal, but is
|
|
||||||
// unfortunate, especially because I mostly did it because I wanted a simple
|
|
||||||
// syntax for no-params that didn't require importing -- the empty tuple fits
|
|
||||||
// that nicely, but I didn't think of it until much later.
|
|
||||||
//
|
|
||||||
// Admittedly, if we did have the generic impl, then we *wouldn't* support the
|
|
||||||
// empty array literal as a parameter, since the `T` there would fail to be
|
|
||||||
// inferred. The error message here would probably be quite bad, and so on
|
|
||||||
// further thought, probably would end up causing *more* surprises, not less.
|
|
||||||
impl Sealed for [&(dyn ToSql + Send + Sync); 0] {}
|
|
||||||
impl Params for [&(dyn ToSql + Send + Sync); 0] {
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.ensure_parameter_count(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sealed for &[&dyn ToSql] {}
|
|
||||||
impl Params for &[&dyn ToSql] {
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.bind_parameters(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: BindIndex, T: ToSql> Sealed for &[(S, T)] {}
|
|
||||||
impl<S: BindIndex, T: ToSql> Params for &[(S, T)] {
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.bind_parameters_named(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manual impls for the empty and singleton tuple, although the rest are covered
|
|
||||||
// by macros.
|
|
||||||
impl Sealed for () {}
|
|
||||||
impl Params for () {
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.ensure_parameter_count(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I'm pretty sure you could tweak the `single_tuple_impl` to accept this.
|
|
||||||
impl<T: ToSql> Sealed for (T,) {}
|
|
||||||
impl<T: ToSql> Params for (T,) {
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.ensure_parameter_count(1)?;
|
|
||||||
stmt.raw_bind_parameter(1, self.0)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! single_tuple_impl {
|
|
||||||
($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {
|
|
||||||
impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: ToSql,)* {}
|
|
||||||
impl<$($ftype,)*> Params for ($($ftype,)*) where $($ftype: ToSql,)* {
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.ensure_parameter_count($count)?;
|
|
||||||
$({
|
|
||||||
debug_assert!($field < $count);
|
|
||||||
stmt.raw_bind_parameter($field + 1, self.$field)?;
|
|
||||||
})+
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use a macro for the rest, but don't bother with trying to implement it
|
|
||||||
// in a single invocation (it's possible to do, but my attempts were almost the
|
|
||||||
// same amount of code as just writing it out this way, and much more dense --
|
|
||||||
// it is a more complicated case than the TryFrom macro we have for row->tuple).
|
|
||||||
//
|
|
||||||
// Note that going up to 16 (rather than the 12 that the impls in the stdlib
|
|
||||||
// usually support) is just because we did the same in the `TryFrom<Row>` impl.
|
|
||||||
// I didn't catch that then, but there's no reason to remove it, and it seems
|
|
||||||
// nice to be consistent here; this way putting data in the database and getting
|
|
||||||
// data out of the database are more symmetric in a (mostly superficial) sense.
|
|
||||||
single_tuple_impl!(2: (0 A), (1 B));
|
|
||||||
single_tuple_impl!(3: (0 A), (1 B), (2 C));
|
|
||||||
single_tuple_impl!(4: (0 A), (1 B), (2 C), (3 D));
|
|
||||||
single_tuple_impl!(5: (0 A), (1 B), (2 C), (3 D), (4 E));
|
|
||||||
single_tuple_impl!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));
|
|
||||||
single_tuple_impl!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));
|
|
||||||
single_tuple_impl!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));
|
|
||||||
single_tuple_impl!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));
|
|
||||||
single_tuple_impl!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));
|
|
||||||
single_tuple_impl!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));
|
|
||||||
single_tuple_impl!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));
|
|
||||||
single_tuple_impl!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));
|
|
||||||
single_tuple_impl!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));
|
|
||||||
single_tuple_impl!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));
|
|
||||||
single_tuple_impl!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));
|
|
||||||
|
|
||||||
macro_rules! impl_for_array_ref {
|
|
||||||
($($N:literal)+) => {$(
|
|
||||||
// These are already generic, and there's a shedload of them, so lets
|
|
||||||
// avoid the compile time hit from making them all inline for now.
|
|
||||||
impl<T: ToSql + ?Sized> Sealed for &[&T; $N] {}
|
|
||||||
impl<T: ToSql + ?Sized> Params for &[&T; $N] {
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.bind_parameters(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<S: BindIndex, T: ToSql + ?Sized> Sealed for &[(S, &T); $N] {}
|
|
||||||
impl<S: BindIndex, T: ToSql + ?Sized> Params for &[(S, &T); $N] {
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.bind_parameters_named(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: ToSql> Sealed for [T; $N] {}
|
|
||||||
impl<T: ToSql> Params for [T; $N] {
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.bind_parameters(&self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
|
|
||||||
// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
|
|
||||||
// note above the impl of `[&dyn ToSql; 0]` for more information.
|
|
||||||
//
|
|
||||||
// Note that this unfortunately means we can't use const generics here, but I
|
|
||||||
// don't really think it matters -- users who hit that can use `params!` anyway.
|
|
||||||
impl_for_array_ref!(
|
|
||||||
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
|
||||||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Adapter type which allows any iterator over [`ToSql`] values to implement
|
|
||||||
/// [`Params`].
|
|
||||||
///
|
|
||||||
/// This struct is created by the [`params_from_iter`] function.
|
|
||||||
///
|
|
||||||
/// This can be useful if you have something like an `&[String]` (of unknown
|
|
||||||
/// length), and you want to use them with an API that wants something
|
|
||||||
/// implementing `Params`. This way, you can avoid having to allocate storage
|
|
||||||
/// for something like a `&[&dyn ToSql]`.
|
|
||||||
///
|
|
||||||
/// This essentially is only ever actually needed when dynamically generating
|
|
||||||
/// SQL — static SQL (by definition) has the number of parameters known
|
|
||||||
/// statically. As dynamically generating SQL is itself pretty advanced, this
|
|
||||||
/// API is itself for advanced use cases (See "Realistic use case" in the
|
|
||||||
/// examples).
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ## Basic usage
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// use rusqlite::{params_from_iter, Connection, Result};
|
|
||||||
/// use std::collections::BTreeSet;
|
|
||||||
///
|
|
||||||
/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
|
|
||||||
/// assert_eq!(ids.len(), 3, "Unrealistic sample code");
|
|
||||||
///
|
|
||||||
/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?1, ?2, ?3)")?;
|
|
||||||
/// let _rows = stmt.query(params_from_iter(ids.iter()))?;
|
|
||||||
///
|
|
||||||
/// // use _rows...
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Realistic use case
|
|
||||||
///
|
|
||||||
/// Here's how you'd use `ParamsFromIter` to call [`Statement::exists`] with a
|
|
||||||
/// dynamic number of parameters.
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// use rusqlite::{Connection, Result};
|
|
||||||
///
|
|
||||||
/// pub fn any_active_users(conn: &Connection, usernames: &[String]) -> Result<bool> {
|
|
||||||
/// if usernames.is_empty() {
|
|
||||||
/// return Ok(false);
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Note: `repeat_vars` never returns anything attacker-controlled, so
|
|
||||||
/// // it's fine to use it in a dynamically-built SQL string.
|
|
||||||
/// let vars = repeat_vars(usernames.len());
|
|
||||||
///
|
|
||||||
/// let sql = format!(
|
|
||||||
/// // In practice this would probably be better as an `EXISTS` query.
|
|
||||||
/// "SELECT 1 FROM user WHERE is_active AND name IN ({}) LIMIT 1",
|
|
||||||
/// vars,
|
|
||||||
/// );
|
|
||||||
/// let mut stmt = conn.prepare(&sql)?;
|
|
||||||
/// stmt.exists(rusqlite::params_from_iter(usernames))
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Helper function to return a comma-separated sequence of `?`.
|
|
||||||
/// // - `repeat_vars(0) => panic!(...)`
|
|
||||||
/// // - `repeat_vars(1) => "?"`
|
|
||||||
/// // - `repeat_vars(2) => "?,?"`
|
|
||||||
/// // - `repeat_vars(3) => "?,?,?"`
|
|
||||||
/// // - ...
|
|
||||||
/// fn repeat_vars(count: usize) -> String {
|
|
||||||
/// assert_ne!(count, 0);
|
|
||||||
/// let mut s = "?,".repeat(count);
|
|
||||||
/// // Remove trailing comma
|
|
||||||
/// s.pop();
|
|
||||||
/// s
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// That is fairly complex, and even so would need even more work to be fully
|
|
||||||
/// production-ready:
|
|
||||||
///
|
|
||||||
/// - production code should ensure `usernames` isn't so large that it will
|
|
||||||
/// surpass [`conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)`][limits],
|
|
||||||
/// chunking if too large. (Note that the limits api requires rusqlite to have
|
|
||||||
/// the "limits" feature).
|
|
||||||
///
|
|
||||||
/// - `repeat_vars` can be implemented in a way that avoids needing to allocate
|
|
||||||
/// a String.
|
|
||||||
///
|
|
||||||
/// - Etc...
|
|
||||||
///
|
|
||||||
/// [limits]: crate::Connection::limit
|
|
||||||
///
|
|
||||||
/// This complexity reflects the fact that `ParamsFromIter` is mainly intended
|
|
||||||
/// for advanced use cases — most of the time you should know how many
|
|
||||||
/// parameters you have statically (and if you don't, you're either doing
|
|
||||||
/// something tricky, or should take a moment to think about the design).
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ParamsFromIter<I>(I);
|
|
||||||
|
|
||||||
/// Constructor function for a [`ParamsFromIter`]. See its documentation for
|
|
||||||
/// more.
|
|
||||||
#[inline]
|
|
||||||
pub fn params_from_iter<I>(iter: I) -> ParamsFromIter<I>
|
|
||||||
where
|
|
||||||
I: IntoIterator,
|
|
||||||
I::Item: ToSql,
|
|
||||||
{
|
|
||||||
ParamsFromIter(iter)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> Sealed for ParamsFromIter<I>
|
|
||||||
where
|
|
||||||
I: IntoIterator,
|
|
||||||
I::Item: ToSql,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> Params for ParamsFromIter<I>
|
|
||||||
where
|
|
||||||
I: IntoIterator,
|
|
||||||
I::Item: ToSql,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
|
||||||
stmt.bind_parameters(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
426
vendor/rusqlite/src/pragma.rs
vendored
426
vendor/rusqlite/src/pragma.rs
vendored
@@ -1,426 +0,0 @@
|
|||||||
//! Pragma helpers
|
|
||||||
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::types::{ToSql, ToSqlOutput, ValueRef};
|
|
||||||
use crate::{Connection, Result, Row};
|
|
||||||
|
|
||||||
pub struct Sql {
|
|
||||||
buf: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sql {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { buf: String::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_pragma(&mut self, schema_name: Option<&str>, pragma_name: &str) -> Result<()> {
|
|
||||||
self.push_keyword("PRAGMA")?;
|
|
||||||
self.push_space();
|
|
||||||
if let Some(schema_name) = schema_name {
|
|
||||||
self.push_schema_name(schema_name);
|
|
||||||
self.push_dot();
|
|
||||||
}
|
|
||||||
self.push_keyword(pragma_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_keyword(&mut self, keyword: &str) -> Result<()> {
|
|
||||||
if !keyword.is_empty() && is_identifier(keyword) {
|
|
||||||
self.buf.push_str(keyword);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(err!(ffi::SQLITE_MISUSE, "Invalid keyword \"{keyword}\""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_schema_name(&mut self, schema_name: &str) {
|
|
||||||
self.push_identifier(schema_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_identifier(&mut self, s: &str) {
|
|
||||||
if is_identifier(s) {
|
|
||||||
self.buf.push_str(s);
|
|
||||||
} else {
|
|
||||||
self.wrap_and_escape(s, '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_value(&mut self, value: &dyn ToSql) -> Result<()> {
|
|
||||||
let value = value.to_sql()?;
|
|
||||||
let value = match value {
|
|
||||||
ToSqlOutput::Borrowed(v) => v,
|
|
||||||
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
|
||||||
#[cfg(any(feature = "blob", feature = "functions", feature = "pointer"))]
|
|
||||||
_ => {
|
|
||||||
return Err(err!(ffi::SQLITE_MISUSE, "Unsupported value \"{value:?}\""));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match value {
|
|
||||||
ValueRef::Integer(i) => {
|
|
||||||
self.push_int(i);
|
|
||||||
}
|
|
||||||
ValueRef::Real(r) => {
|
|
||||||
self.push_real(r);
|
|
||||||
}
|
|
||||||
ValueRef::Text(s) => {
|
|
||||||
let s = std::str::from_utf8(s)?;
|
|
||||||
self.push_string_literal(s);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(err!(ffi::SQLITE_MISUSE, "Unsupported value \"{value:?}\""));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_string_literal(&mut self, s: &str) {
|
|
||||||
self.wrap_and_escape(s, '\'');
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_int(&mut self, i: i64) {
|
|
||||||
self.buf.push_str(&i.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_real(&mut self, f: f64) {
|
|
||||||
self.buf.push_str(&f.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_space(&mut self) {
|
|
||||||
self.buf.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_dot(&mut self) {
|
|
||||||
self.buf.push('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_equal_sign(&mut self) {
|
|
||||||
self.buf.push('=');
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_brace(&mut self) {
|
|
||||||
self.buf.push('(');
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close_brace(&mut self) {
|
|
||||||
self.buf.push(')');
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
&self.buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_and_escape(&mut self, s: &str, quote: char) {
|
|
||||||
self.buf.push(quote);
|
|
||||||
let chars = s.chars();
|
|
||||||
for ch in chars {
|
|
||||||
// escape `quote` by doubling it
|
|
||||||
if ch == quote {
|
|
||||||
self.buf.push(ch);
|
|
||||||
}
|
|
||||||
self.buf.push(ch);
|
|
||||||
}
|
|
||||||
self.buf.push(quote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Sql {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Query the current value of `pragma_name`.
|
|
||||||
///
|
|
||||||
/// Some pragmas will return multiple rows/values which cannot be retrieved
|
|
||||||
/// with this method.
|
|
||||||
///
|
|
||||||
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
|
||||||
/// `SELECT user_version FROM pragma_user_version;`
|
|
||||||
pub fn pragma_query_value<T, F>(
|
|
||||||
&self,
|
|
||||||
schema_name: Option<&str>,
|
|
||||||
pragma_name: &str,
|
|
||||||
f: F,
|
|
||||||
) -> Result<T>
|
|
||||||
where
|
|
||||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
|
||||||
{
|
|
||||||
let mut query = Sql::new();
|
|
||||||
query.push_pragma(schema_name, pragma_name)?;
|
|
||||||
self.query_row(&query, [], f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the current rows/values of `pragma_name`.
|
|
||||||
///
|
|
||||||
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
|
||||||
/// `SELECT * FROM pragma_collation_list;`
|
|
||||||
pub fn pragma_query<F>(
|
|
||||||
&self,
|
|
||||||
schema_name: Option<&str>,
|
|
||||||
pragma_name: &str,
|
|
||||||
mut f: F,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<()>,
|
|
||||||
{
|
|
||||||
let mut query = Sql::new();
|
|
||||||
query.push_pragma(schema_name, pragma_name)?;
|
|
||||||
let mut stmt = self.prepare(&query)?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
while let Some(result_row) = rows.next()? {
|
|
||||||
let row = result_row;
|
|
||||||
f(row)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the current value(s) of `pragma_name` associated to
|
|
||||||
/// `pragma_value`.
|
|
||||||
///
|
|
||||||
/// This method can be used with query-only pragmas which need an argument
|
|
||||||
/// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s)
|
|
||||||
/// (e.g. `integrity_check`).
|
|
||||||
///
|
|
||||||
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
|
||||||
/// `SELECT * FROM pragma_table_info(?1);`
|
|
||||||
pub fn pragma<F, V>(
|
|
||||||
&self,
|
|
||||||
schema_name: Option<&str>,
|
|
||||||
pragma_name: &str,
|
|
||||||
pragma_value: V,
|
|
||||||
mut f: F,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<()>,
|
|
||||||
V: ToSql,
|
|
||||||
{
|
|
||||||
let mut sql = Sql::new();
|
|
||||||
sql.push_pragma(schema_name, pragma_name)?;
|
|
||||||
// The argument may be either in parentheses
|
|
||||||
// or it may be separated from the pragma name by an equal sign.
|
|
||||||
// The two syntaxes yield identical results.
|
|
||||||
sql.open_brace();
|
|
||||||
sql.push_value(&pragma_value)?;
|
|
||||||
sql.close_brace();
|
|
||||||
let mut stmt = self.prepare(&sql)?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
while let Some(result_row) = rows.next()? {
|
|
||||||
let row = result_row;
|
|
||||||
f(row)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a new value to `pragma_name`.
|
|
||||||
///
|
|
||||||
/// Some pragmas will return the updated value which cannot be retrieved
|
|
||||||
/// with this method.
|
|
||||||
pub fn pragma_update<V>(
|
|
||||||
&self,
|
|
||||||
schema_name: Option<&str>,
|
|
||||||
pragma_name: &str,
|
|
||||||
pragma_value: V,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
V: ToSql,
|
|
||||||
{
|
|
||||||
let mut sql = Sql::new();
|
|
||||||
sql.push_pragma(schema_name, pragma_name)?;
|
|
||||||
// The argument may be either in parentheses
|
|
||||||
// or it may be separated from the pragma name by an equal sign.
|
|
||||||
// The two syntaxes yield identical results.
|
|
||||||
sql.push_equal_sign();
|
|
||||||
sql.push_value(&pragma_value)?;
|
|
||||||
self.execute_batch(&sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a new value to `pragma_name` and return the updated value.
|
|
||||||
///
|
|
||||||
/// Only few pragmas automatically return the updated value.
|
|
||||||
pub fn pragma_update_and_check<F, T, V>(
|
|
||||||
&self,
|
|
||||||
schema_name: Option<&str>,
|
|
||||||
pragma_name: &str,
|
|
||||||
pragma_value: V,
|
|
||||||
f: F,
|
|
||||||
) -> Result<T>
|
|
||||||
where
|
|
||||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
|
||||||
V: ToSql,
|
|
||||||
{
|
|
||||||
let mut sql = Sql::new();
|
|
||||||
sql.push_pragma(schema_name, pragma_name)?;
|
|
||||||
// The argument may be either in parentheses
|
|
||||||
// or it may be separated from the pragma name by an equal sign.
|
|
||||||
// The two syntaxes yield identical results.
|
|
||||||
sql.push_equal_sign();
|
|
||||||
sql.push_value(&pragma_value)?;
|
|
||||||
self.query_row(&sql, [], f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_identifier(s: &str) -> bool {
|
|
||||||
let chars = s.char_indices();
|
|
||||||
for (i, ch) in chars {
|
|
||||||
if i == 0 {
|
|
||||||
if !is_identifier_start(ch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if !is_identifier_continue(ch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_identifier_start(c: char) -> bool {
|
|
||||||
c.is_ascii_uppercase() || c == '_' || c.is_ascii_lowercase() || c > '\x7F'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_identifier_continue(c: char) -> bool {
|
|
||||||
c == '$'
|
|
||||||
|| c.is_ascii_digit()
|
|
||||||
|| c.is_ascii_uppercase()
|
|
||||||
|| c == '_'
|
|
||||||
|| c.is_ascii_lowercase()
|
|
||||||
|| c > '\x7F'
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::Sql;
|
|
||||||
use crate::pragma;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_query_value() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let user_version: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
|
||||||
assert_eq!(0, user_version);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_func_query_value() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let user_version: i32 =
|
|
||||||
db.one_column("SELECT user_version FROM pragma_user_version", [])?;
|
|
||||||
assert_eq!(0, user_version);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_query_no_schema() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let mut user_version = -1;
|
|
||||||
db.pragma_query(None, "user_version", |row| {
|
|
||||||
user_version = row.get(0)?;
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
assert_eq!(0, user_version);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_query_with_schema() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let mut user_version = -1;
|
|
||||||
db.pragma_query(Some("main"), "user_version", |row| {
|
|
||||||
user_version = row.get(0)?;
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
assert_eq!(0, user_version);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let mut columns = Vec::new();
|
|
||||||
db.pragma(None, "table_info", "sqlite_master", |row| {
|
|
||||||
let column: String = row.get(1)?;
|
|
||||||
columns.push(column);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
assert_eq!(5, columns.len());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_func() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?1)")?;
|
|
||||||
let mut columns = Vec::new();
|
|
||||||
let mut rows = table_info.query(["sqlite_master"])?;
|
|
||||||
|
|
||||||
while let Some(row) = rows.next()? {
|
|
||||||
let column: String = row.get(1)?;
|
|
||||||
columns.push(column);
|
|
||||||
}
|
|
||||||
assert_eq!(5, columns.len());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_update() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.pragma_update(None, "user_version", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pragma_update_and_check() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let journal_mode: String =
|
|
||||||
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
|
|
||||||
assert!(
|
|
||||||
journal_mode == "off" || journal_mode == "memory",
|
|
||||||
"mode: {journal_mode:?}"
|
|
||||||
);
|
|
||||||
// Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
|
|
||||||
let mode =
|
|
||||||
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get::<_, String>(0))?;
|
|
||||||
assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
|
|
||||||
|
|
||||||
let param: &dyn crate::ToSql = &"OFF";
|
|
||||||
let mode =
|
|
||||||
db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?;
|
|
||||||
assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn is_identifier() {
|
|
||||||
assert!(pragma::is_identifier("full"));
|
|
||||||
assert!(pragma::is_identifier("r2d2"));
|
|
||||||
assert!(!pragma::is_identifier("sp ce"));
|
|
||||||
assert!(!pragma::is_identifier("semi;colon"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn double_quote() {
|
|
||||||
let mut sql = Sql::new();
|
|
||||||
sql.push_schema_name(r#"schema";--"#);
|
|
||||||
assert_eq!(r#""schema"";--""#, sql.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn wrap_and_escape() {
|
|
||||||
let mut sql = Sql::new();
|
|
||||||
sql.push_string_literal("value'; --");
|
|
||||||
assert_eq!("'value''; --'", sql.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn locking_mode() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.pragma_update(None, "locking_mode", "exclusive")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
282
vendor/rusqlite/src/raw_statement.rs
vendored
282
vendor/rusqlite/src/raw_statement.rs
vendored
@@ -1,282 +0,0 @@
|
|||||||
use super::ffi;
|
|
||||||
use super::StatementStatus;
|
|
||||||
use crate::util::ParamIndexCache;
|
|
||||||
use crate::util::SqliteMallocString;
|
|
||||||
use std::ffi::{c_int, CStr};
|
|
||||||
use std::ptr;
|
|
||||||
#[cfg(feature = "cache")]
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RawStatement {
|
|
||||||
ptr: *mut ffi::sqlite3_stmt,
|
|
||||||
// Cached indices of named parameters, computed on the fly.
|
|
||||||
cache: ParamIndexCache,
|
|
||||||
// Cached SQL (trimmed) that we use as the key when we're in the statement
|
|
||||||
// cache. This is None for statements which didn't come from the statement
|
|
||||||
// cache.
|
|
||||||
//
|
|
||||||
// This is probably the same as `self.sql()` in most cases, but we don't
|
|
||||||
// care either way -- It's a better cache key as it is anyway since it's the
|
|
||||||
// actual source we got from rust.
|
|
||||||
//
|
|
||||||
// One example of a case where the result of `sqlite_sql` and the value in
|
|
||||||
// `statement_cache_key` might differ is if the statement has a `tail`.
|
|
||||||
#[cfg(feature = "cache")]
|
|
||||||
statement_cache_key: Option<Arc<str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawStatement {
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt) -> Self {
|
|
||||||
Self {
|
|
||||||
ptr: stmt,
|
|
||||||
cache: ParamIndexCache::default(),
|
|
||||||
#[cfg(feature = "cache")]
|
|
||||||
statement_cache_key: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_null(&self) -> bool {
|
|
||||||
self.ptr.is_null()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "cache")]
|
|
||||||
pub(crate) fn set_statement_cache_key(&mut self, p: impl Into<Arc<str>>) {
|
|
||||||
self.statement_cache_key = Some(p.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "cache")]
|
|
||||||
pub(crate) fn statement_cache_key(&self) -> Option<Arc<str>> {
|
|
||||||
self.statement_cache_key.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
|
|
||||||
self.ptr
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn column_count(&self) -> usize {
|
|
||||||
// Note: Can't cache this as it changes if the schema is altered.
|
|
||||||
unsafe { ffi::sqlite3_column_count(self.ptr) as usize }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn column_type(&self, idx: usize) -> c_int {
|
|
||||||
unsafe { ffi::sqlite3_column_type(self.ptr, idx as c_int) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
pub fn column_database_name(&self, idx: usize) -> Option<&CStr> {
|
|
||||||
unsafe {
|
|
||||||
let db_name = ffi::sqlite3_column_database_name(self.ptr, idx as c_int);
|
|
||||||
if db_name.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(CStr::from_ptr(db_name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
pub fn column_table_name(&self, idx: usize) -> Option<&CStr> {
|
|
||||||
unsafe {
|
|
||||||
let tbl_name = ffi::sqlite3_column_table_name(self.ptr, idx as c_int);
|
|
||||||
if tbl_name.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(CStr::from_ptr(tbl_name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "column_metadata")]
|
|
||||||
pub fn column_origin_name(&self, idx: usize) -> Option<&CStr> {
|
|
||||||
unsafe {
|
|
||||||
let origin_name = ffi::sqlite3_column_origin_name(self.ptr, idx as c_int);
|
|
||||||
if origin_name.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(CStr::from_ptr(origin_name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "column_decltype")]
|
|
||||||
pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
|
|
||||||
unsafe {
|
|
||||||
let decltype = ffi::sqlite3_column_decltype(self.ptr, idx as c_int);
|
|
||||||
if decltype.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(CStr::from_ptr(decltype))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn column_name(&self, idx: usize) -> Option<&CStr> {
|
|
||||||
let idx = idx as c_int;
|
|
||||||
if idx < 0 || idx >= self.column_count() as c_int {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
let ptr = ffi::sqlite3_column_name(self.ptr, idx);
|
|
||||||
// If ptr is null here, it's an OOM, so there's probably nothing
|
|
||||||
// meaningful we can do. Just assert instead of returning None.
|
|
||||||
assert!(
|
|
||||||
!ptr.is_null(),
|
|
||||||
"Null pointer from sqlite3_column_name: Out of memory?"
|
|
||||||
);
|
|
||||||
Some(CStr::from_ptr(ptr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(not(feature = "unlock_notify"))]
|
|
||||||
pub fn step(&self) -> c_int {
|
|
||||||
unsafe { ffi::sqlite3_step(self.ptr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "unlock_notify")]
|
|
||||||
pub fn step(&self) -> c_int {
|
|
||||||
use crate::unlock_notify;
|
|
||||||
let mut db = ptr::null_mut::<ffi::sqlite3>();
|
|
||||||
loop {
|
|
||||||
unsafe {
|
|
||||||
let mut rc = ffi::sqlite3_step(self.ptr);
|
|
||||||
// Bail out early for success and errors unrelated to locking. We
|
|
||||||
// still need check `is_locked` after this, but checking now lets us
|
|
||||||
// avoid one or two (admittedly cheap) calls into SQLite that we
|
|
||||||
// don't need to make.
|
|
||||||
if (rc & 0xff) != ffi::SQLITE_LOCKED {
|
|
||||||
break rc;
|
|
||||||
}
|
|
||||||
if db.is_null() {
|
|
||||||
db = ffi::sqlite3_db_handle(self.ptr);
|
|
||||||
}
|
|
||||||
if !unlock_notify::is_locked(db, rc) {
|
|
||||||
break rc;
|
|
||||||
}
|
|
||||||
rc = unlock_notify::wait_for_unlock_notify(db);
|
|
||||||
if rc != ffi::SQLITE_OK {
|
|
||||||
break rc;
|
|
||||||
}
|
|
||||||
self.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn reset(&self) -> c_int {
|
|
||||||
unsafe { ffi::sqlite3_reset(self.ptr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bind_parameter_count(&self) -> usize {
|
|
||||||
unsafe { ffi::sqlite3_bind_parameter_count(self.ptr) as usize }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bind_parameter_index(&self, name: &str) -> Option<usize> {
|
|
||||||
self.cache.get_or_insert_with(name, |param_cstr| {
|
|
||||||
let r = unsafe { ffi::sqlite3_bind_parameter_index(self.ptr, param_cstr.as_ptr()) };
|
|
||||||
match r {
|
|
||||||
0 => None,
|
|
||||||
i => Some(i as usize),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bind_parameter_name(&self, index: i32) -> Option<&CStr> {
|
|
||||||
unsafe {
|
|
||||||
let name = ffi::sqlite3_bind_parameter_name(self.ptr, index);
|
|
||||||
if name.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(CStr::from_ptr(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn clear_bindings(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_clear_bindings(self.ptr);
|
|
||||||
} // rc is always SQLITE_OK
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn sql(&self) -> Option<&CStr> {
|
|
||||||
if self.ptr.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.ptr)) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn finalize(mut self) -> c_int {
|
|
||||||
self.finalize_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn finalize_(&mut self) -> c_int {
|
|
||||||
let r = unsafe { ffi::sqlite3_finalize(self.ptr) };
|
|
||||||
self.ptr = ptr::null_mut();
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
// does not work for PRAGMA
|
|
||||||
#[inline]
|
|
||||||
pub fn readonly(&self) -> bool {
|
|
||||||
unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn expanded_sql(&self) -> Option<SqliteMallocString> {
|
|
||||||
unsafe { expanded_sql(self.ptr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
|
|
||||||
unsafe { stmt_status(self.ptr, status, reset) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_explain(&self) -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_stmt_isexplain(self.ptr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO sqlite3_normalized_sql (https://sqlite.org/c3ref/expanded_sql.html) // 3.27.0 + SQLITE_ENABLE_NORMALIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn expanded_sql(ptr: *mut ffi::sqlite3_stmt) -> Option<SqliteMallocString> {
|
|
||||||
SqliteMallocString::from_raw(ffi::sqlite3_expanded_sql(ptr))
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn stmt_status(
|
|
||||||
ptr: *mut ffi::sqlite3_stmt,
|
|
||||||
status: StatementStatus,
|
|
||||||
reset: bool,
|
|
||||||
) -> i32 {
|
|
||||||
assert!(!ptr.is_null());
|
|
||||||
ffi::sqlite3_stmt_status(ptr, status as i32, reset as i32)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for RawStatement {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.finalize_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
680
vendor/rusqlite/src/row.rs
vendored
680
vendor/rusqlite/src/row.rs
vendored
@@ -1,680 +0,0 @@
|
|||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
|
||||||
use std::convert;
|
|
||||||
|
|
||||||
use super::{Error, Result, Statement};
|
|
||||||
use crate::types::{FromSql, FromSqlError, ValueRef};
|
|
||||||
|
|
||||||
/// A handle (lazy fallible streaming iterator) for the resulting rows of a query.
|
|
||||||
#[must_use = "Rows is lazy and will do nothing unless consumed"]
|
|
||||||
pub struct Rows<'stmt> {
|
|
||||||
pub(crate) stmt: Option<&'stmt Statement<'stmt>>,
|
|
||||||
row: Option<Row<'stmt>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'stmt> Rows<'stmt> {
|
|
||||||
#[inline]
|
|
||||||
fn reset(&mut self) -> Result<()> {
|
|
||||||
if let Some(stmt) = self.stmt.take() {
|
|
||||||
stmt.reset()
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to get the next row from the query. Returns `Ok(Some(Row))` if
|
|
||||||
/// there is another row, `Err(...)` if there was an error
|
|
||||||
/// getting the next row, and `Ok(None)` if all rows have been retrieved.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// This interface is not compatible with Rust's `Iterator` trait, because
|
|
||||||
/// the lifetime of the returned row is tied to the lifetime of `self`.
|
|
||||||
/// This is a fallible "streaming iterator". For a more natural interface,
|
|
||||||
/// consider using [`query_map`](Statement::query_map) or
|
|
||||||
/// [`query_and_then`](Statement::query_and_then) instead, which
|
|
||||||
/// return types that implement `Iterator`.
|
|
||||||
#[expect(clippy::should_implement_trait)] // cannot implement Iterator
|
|
||||||
#[inline]
|
|
||||||
pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
|
|
||||||
self.advance()?;
|
|
||||||
Ok((*self).get())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map over this `Rows`, converting it to a [`Map`], which
|
|
||||||
/// implements `FallibleIterator`.
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// use fallible_iterator::FallibleIterator;
|
|
||||||
/// # use rusqlite::{Result, Statement};
|
|
||||||
/// fn query(stmt: &mut Statement) -> Result<Vec<i64>> {
|
|
||||||
/// let rows = stmt.query([])?;
|
|
||||||
/// rows.map(|r| r.get(0)).collect()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// FIXME Hide FallibleStreamingIterator::map
|
|
||||||
#[inline]
|
|
||||||
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<B>,
|
|
||||||
{
|
|
||||||
Map { rows: self, f }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map over this `Rows`, converting it to a [`MappedRows`], which
|
|
||||||
/// implements `Iterator`.
|
|
||||||
#[inline]
|
|
||||||
pub fn mapped<F, B>(self, f: F) -> MappedRows<'stmt, F>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<B>,
|
|
||||||
{
|
|
||||||
MappedRows { rows: self, map: f }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map over this `Rows` with a fallible function, converting it to a
|
|
||||||
/// [`AndThenRows`], which implements `Iterator` (instead of
|
|
||||||
/// `FallibleStreamingIterator`).
|
|
||||||
#[inline]
|
|
||||||
pub fn and_then<F, T, E>(self, f: F) -> AndThenRows<'stmt, F>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<T, E>,
|
|
||||||
{
|
|
||||||
AndThenRows { rows: self, map: f }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Give access to the underlying statement
|
|
||||||
#[must_use]
|
|
||||||
pub fn as_ref(&self) -> Option<&Statement<'stmt>> {
|
|
||||||
self.stmt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'stmt> Rows<'stmt> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Self {
|
|
||||||
Rows {
|
|
||||||
stmt: Some(stmt),
|
|
||||||
row: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
|
|
||||||
match self.next()? {
|
|
||||||
Some(row) => Ok(row),
|
|
||||||
None => Err(Error::QueryReturnedNoRows),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Rows<'_> {
|
|
||||||
#[expect(unused_must_use)]
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `F` is used to transform the _streaming_ iterator into a _fallible_
|
|
||||||
/// iterator.
|
|
||||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
|
||||||
pub struct Map<'stmt, F> {
|
|
||||||
rows: Rows<'stmt>,
|
|
||||||
f: F,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F, B> FallibleIterator for Map<'_, F>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<B>,
|
|
||||||
{
|
|
||||||
type Item = B;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Result<Option<B>> {
|
|
||||||
match self.rows.next()? {
|
|
||||||
Some(v) => Ok(Some((self.f)(v)?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over the mapped resulting rows of a query.
|
|
||||||
///
|
|
||||||
/// `F` is used to transform the _streaming_ iterator into a _standard_
|
|
||||||
/// iterator.
|
|
||||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
|
||||||
pub struct MappedRows<'stmt, F> {
|
|
||||||
rows: Rows<'stmt>,
|
|
||||||
map: F,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, F> Iterator for MappedRows<'_, F>
|
|
||||||
where
|
|
||||||
F: FnMut(&Row<'_>) -> Result<T>,
|
|
||||||
{
|
|
||||||
type Item = Result<T>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<Result<T>> {
|
|
||||||
let map = &mut self.map;
|
|
||||||
self.rows
|
|
||||||
.next()
|
|
||||||
.transpose()
|
|
||||||
.map(|row_result| row_result.and_then(map))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over the mapped resulting rows of a query, with an Error type
|
|
||||||
/// unifying with Error.
|
|
||||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
|
||||||
pub struct AndThenRows<'stmt, F> {
|
|
||||||
rows: Rows<'stmt>,
|
|
||||||
map: F,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E, F> Iterator for AndThenRows<'_, F>
|
|
||||||
where
|
|
||||||
E: From<Error>,
|
|
||||||
F: FnMut(&Row<'_>) -> Result<T, E>,
|
|
||||||
{
|
|
||||||
type Item = Result<T, E>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let map = &mut self.map;
|
|
||||||
self.rows
|
|
||||||
.next()
|
|
||||||
.transpose()
|
|
||||||
.map(|row_result| row_result.map_err(E::from).and_then(map))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `FallibleStreamingIterator` differs from the standard library's `Iterator`
|
|
||||||
/// in two ways:
|
|
||||||
/// * each call to `next` (`sqlite3_step`) can fail.
|
|
||||||
/// * returned `Row` is valid until `next` is called again or `Statement` is
|
|
||||||
/// reset or finalized.
|
|
||||||
///
|
|
||||||
/// While these iterators cannot be used with Rust `for` loops, `while let`
|
|
||||||
/// loops offer a similar level of ergonomics:
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Result, Statement};
|
|
||||||
/// fn query(stmt: &mut Statement) -> Result<()> {
|
|
||||||
/// let mut rows = stmt.query([])?;
|
|
||||||
/// while let Some(row) = rows.next()? {
|
|
||||||
/// // scan columns value
|
|
||||||
/// }
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
|
|
||||||
type Item = Row<'stmt>;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn advance(&mut self) -> Result<()> {
|
|
||||||
if let Some(stmt) = self.stmt {
|
|
||||||
match stmt.step() {
|
|
||||||
Ok(true) => {
|
|
||||||
self.row = Some(Row { stmt });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Ok(false) => {
|
|
||||||
let r = self.reset();
|
|
||||||
self.row = None;
|
|
||||||
r
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let _ = self.reset(); // prevents infinite loop on error
|
|
||||||
self.row = None;
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.row = None;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get(&self) -> Option<&Row<'stmt>> {
|
|
||||||
self.row.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A single result row of a query.
|
|
||||||
pub struct Row<'stmt> {
|
|
||||||
pub(crate) stmt: &'stmt Statement<'stmt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Row<'_> {
|
|
||||||
/// Get the value of a particular column of the result row.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if calling [`row.get(idx)`](Row::get) would return an error,
|
|
||||||
/// including:
|
|
||||||
///
|
|
||||||
/// * If the underlying SQLite column type is not a valid type as a source
|
|
||||||
/// for `T`
|
|
||||||
/// * If the underlying SQLite integral value is outside the range
|
|
||||||
/// representable by `T`
|
|
||||||
/// * If `idx` is outside the range of columns in the returned query
|
|
||||||
#[track_caller]
|
|
||||||
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
|
|
||||||
self.get(idx).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of a particular column of the result row.
|
|
||||||
///
|
|
||||||
/// ## Failure
|
|
||||||
///
|
|
||||||
/// Returns an `Error::InvalidColumnType` if the underlying SQLite column
|
|
||||||
/// type is not a valid type as a source for `T`.
|
|
||||||
///
|
|
||||||
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
|
||||||
/// column range for this row.
|
|
||||||
///
|
|
||||||
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
|
||||||
/// name for this row.
|
|
||||||
///
|
|
||||||
/// If the result type is i128 (which requires the `i128_blob` feature to be
|
|
||||||
/// enabled), and the underlying SQLite column is a blob whose size is not
|
|
||||||
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
|
||||||
let idx = idx.idx(self.stmt)?;
|
|
||||||
let value = self.stmt.value_ref(idx);
|
|
||||||
FromSql::column_result(value).map_err(|err| match err {
|
|
||||||
FromSqlError::InvalidType => Error::InvalidColumnType(
|
|
||||||
idx,
|
|
||||||
self.stmt.column_name_unwrap(idx).into(),
|
|
||||||
value.data_type(),
|
|
||||||
),
|
|
||||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
|
||||||
FromSqlError::Utf8Error(err) => Error::Utf8Error(idx, err),
|
|
||||||
FromSqlError::Other(err) => {
|
|
||||||
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
|
||||||
}
|
|
||||||
FromSqlError::InvalidBlobSize { .. } => {
|
|
||||||
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of a particular column of the result row as a `ValueRef`,
|
|
||||||
/// allowing data to be read out of a row without copying.
|
|
||||||
///
|
|
||||||
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
|
||||||
/// its lifetime. This means that while this method is completely safe,
|
|
||||||
/// it can be somewhat difficult to use, and most callers will be better
|
|
||||||
/// served by [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
|
|
||||||
///
|
|
||||||
/// ## Failure
|
|
||||||
///
|
|
||||||
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
|
||||||
/// column range for this row.
|
|
||||||
///
|
|
||||||
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
|
||||||
/// name for this row.
|
|
||||||
pub fn get_ref<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
|
|
||||||
let idx = idx.idx(self.stmt)?;
|
|
||||||
// Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)`
|
|
||||||
// returns) to `ValueRef<'a>` is needed because it's only valid until
|
|
||||||
// the next call to sqlite3_step.
|
|
||||||
let val_ref = self.stmt.value_ref(idx);
|
|
||||||
Ok(val_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of a particular column of the result row as a `ValueRef`,
|
|
||||||
/// allowing data to be read out of a row without copying.
|
|
||||||
///
|
|
||||||
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
|
||||||
/// its lifetime. This means that while this method is completely safe,
|
|
||||||
/// it can be difficult to use, and most callers will be better served by
|
|
||||||
/// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an
|
|
||||||
/// error, including:
|
|
||||||
///
|
|
||||||
/// * If `idx` is outside the range of columns in the returned query.
|
|
||||||
/// * If `idx` is not a valid column name for this row.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn get_ref_unwrap<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
|
|
||||||
self.get_ref(idx).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return raw pointer at `idx`
|
|
||||||
/// # Safety
|
|
||||||
/// This function is unsafe because it uses raw pointer and cast
|
|
||||||
#[cfg(feature = "pointer")]
|
|
||||||
pub unsafe fn get_pointer<I: RowIndex, T: 'static>(
|
|
||||||
&self,
|
|
||||||
idx: I,
|
|
||||||
ptr_type: &'static std::ffi::CStr,
|
|
||||||
) -> Result<Option<&T>> {
|
|
||||||
let idx = idx.idx(self.stmt)?;
|
|
||||||
debug_assert_eq!(self.stmt.stmt.column_type(idx), super::ffi::SQLITE_NULL);
|
|
||||||
let sv = super::ffi::sqlite3_column_value(self.stmt.stmt.ptr(), idx as std::ffi::c_int);
|
|
||||||
Ok(if sv.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
super::ffi::sqlite3_value_pointer(sv, ptr_type.as_ptr())
|
|
||||||
.cast::<T>()
|
|
||||||
.as_ref()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
|
|
||||||
fn as_ref(&self) -> &Statement<'stmt> {
|
|
||||||
self.stmt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Debug `Row` like an ordered `Map<Result<&str>, Result<(Type, ValueRef)>>`
|
|
||||||
/// with column name as key except that for `Type::Blob` only its size is
|
|
||||||
/// printed (not its content).
|
|
||||||
impl std::fmt::Debug for Row<'_> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut dm = f.debug_map();
|
|
||||||
for c in 0..self.stmt.column_count() {
|
|
||||||
let name = self.stmt.column_name(c).expect("valid column index");
|
|
||||||
dm.key(&name);
|
|
||||||
let value = self.get_ref(c);
|
|
||||||
match value {
|
|
||||||
Ok(value) => {
|
|
||||||
let dt = value.data_type();
|
|
||||||
match value {
|
|
||||||
ValueRef::Null => {
|
|
||||||
dm.value(&(dt, ()));
|
|
||||||
}
|
|
||||||
ValueRef::Integer(i) => {
|
|
||||||
dm.value(&(dt, i));
|
|
||||||
}
|
|
||||||
ValueRef::Real(f) => {
|
|
||||||
dm.value(&(dt, f));
|
|
||||||
}
|
|
||||||
ValueRef::Text(s) => {
|
|
||||||
dm.value(&(dt, String::from_utf8_lossy(s)));
|
|
||||||
}
|
|
||||||
ValueRef::Blob(b) => {
|
|
||||||
dm.value(&(dt, b.len()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(ref _err) => {
|
|
||||||
dm.value(&value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dm.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod sealed {
|
|
||||||
/// This trait exists just to ensure that the only impls of `trait RowIndex`
|
|
||||||
/// that are allowed are ones in this crate.
|
|
||||||
pub trait Sealed {}
|
|
||||||
impl Sealed for usize {}
|
|
||||||
impl Sealed for &str {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait implemented by types that can index into columns of a row.
|
|
||||||
///
|
|
||||||
/// It is only implemented for `usize` and `&str`.
|
|
||||||
pub trait RowIndex: sealed::Sealed {
|
|
||||||
/// Returns the index of the appropriate column, or `Error` if no such
|
|
||||||
/// column exists.
|
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RowIndex for usize {
|
|
||||||
#[inline]
|
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
|
||||||
if *self >= stmt.column_count() {
|
|
||||||
Err(Error::InvalidColumnIndex(*self))
|
|
||||||
} else {
|
|
||||||
Ok(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RowIndex for &'_ str {
|
|
||||||
#[inline]
|
|
||||||
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
|
||||||
stmt.column_index(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! tuple_try_from_row {
|
|
||||||
($($field:ident),*) => {
|
|
||||||
impl<'a, $($field,)*> convert::TryFrom<&'a Row<'a>> for ($($field,)*) where $($field: FromSql,)* {
|
|
||||||
type Error = crate::Error;
|
|
||||||
|
|
||||||
// we end with index += 1, which rustc warns about
|
|
||||||
// unused_variables and unused_mut are allowed for ()
|
|
||||||
#[allow(unused_assignments, unused_variables, unused_mut)]
|
|
||||||
fn try_from(row: &'a Row<'a>) -> Result<Self> {
|
|
||||||
let mut index = 0;
|
|
||||||
$(
|
|
||||||
#[expect(non_snake_case)]
|
|
||||||
let $field = row.get::<_, $field>(index)?;
|
|
||||||
index += 1;
|
|
||||||
)*
|
|
||||||
Ok(($($field,)*))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! tuples_try_from_row {
|
|
||||||
() => {
|
|
||||||
// not very useful, but maybe some other macro users will find this helpful
|
|
||||||
tuple_try_from_row!();
|
|
||||||
};
|
|
||||||
($first:ident $(, $remaining:ident)*) => {
|
|
||||||
tuple_try_from_row!($first $(, $remaining)*);
|
|
||||||
tuples_try_from_row!($($remaining),*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
tuples_try_from_row!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_try_from_row_for_tuple_1() -> Result<()> {
|
|
||||||
use crate::ToSql;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
conn.execute(
|
|
||||||
"CREATE TABLE test (a INTEGER)",
|
|
||||||
crate::params_from_iter(std::iter::empty::<&dyn ToSql>()),
|
|
||||||
)?;
|
|
||||||
conn.execute("INSERT INTO test VALUES (42)", [])?;
|
|
||||||
let val = conn.query_row("SELECT a FROM test", [], |row| <(u32,)>::try_from(row))?;
|
|
||||||
assert_eq!(val, (42,));
|
|
||||||
let fail = conn.query_row("SELECT a FROM test", [], |row| <(u32, u32)>::try_from(row));
|
|
||||||
fail.unwrap_err();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_try_from_row_for_tuple_2() -> Result<()> {
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
conn.execute("CREATE TABLE test (a INTEGER, b INTEGER)", [])?;
|
|
||||||
conn.execute("INSERT INTO test VALUES (42, 47)", [])?;
|
|
||||||
let val = conn.query_row("SELECT a, b FROM test", [], |row| {
|
|
||||||
<(u32, u32)>::try_from(row)
|
|
||||||
})?;
|
|
||||||
assert_eq!(val, (42, 47));
|
|
||||||
let fail = conn.query_row("SELECT a, b FROM test", [], |row| {
|
|
||||||
<(u32, u32, u32)>::try_from(row)
|
|
||||||
});
|
|
||||||
fail.unwrap_err();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_try_from_row_for_tuple_16() -> Result<()> {
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
let create_table = "CREATE TABLE test (
|
|
||||||
a INTEGER,
|
|
||||||
b INTEGER,
|
|
||||||
c INTEGER,
|
|
||||||
d INTEGER,
|
|
||||||
e INTEGER,
|
|
||||||
f INTEGER,
|
|
||||||
g INTEGER,
|
|
||||||
h INTEGER,
|
|
||||||
i INTEGER,
|
|
||||||
j INTEGER,
|
|
||||||
k INTEGER,
|
|
||||||
l INTEGER,
|
|
||||||
m INTEGER,
|
|
||||||
n INTEGER,
|
|
||||||
o INTEGER,
|
|
||||||
p INTEGER
|
|
||||||
)";
|
|
||||||
|
|
||||||
let insert_values = "INSERT INTO test VALUES (
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
10,
|
|
||||||
11,
|
|
||||||
12,
|
|
||||||
13,
|
|
||||||
14,
|
|
||||||
15
|
|
||||||
)";
|
|
||||||
|
|
||||||
type BigTuple = (
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
u32,
|
|
||||||
);
|
|
||||||
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
conn.execute(create_table, [])?;
|
|
||||||
conn.execute(insert_values, [])?;
|
|
||||||
let val = conn.query_row("SELECT * FROM test", [], |row| BigTuple::try_from(row))?;
|
|
||||||
// Debug is not implemented for tuples of 16
|
|
||||||
assert_eq!(val.0, 0);
|
|
||||||
assert_eq!(val.1, 1);
|
|
||||||
assert_eq!(val.2, 2);
|
|
||||||
assert_eq!(val.3, 3);
|
|
||||||
assert_eq!(val.4, 4);
|
|
||||||
assert_eq!(val.5, 5);
|
|
||||||
assert_eq!(val.6, 6);
|
|
||||||
assert_eq!(val.7, 7);
|
|
||||||
assert_eq!(val.8, 8);
|
|
||||||
assert_eq!(val.9, 9);
|
|
||||||
assert_eq!(val.10, 10);
|
|
||||||
assert_eq!(val.11, 11);
|
|
||||||
assert_eq!(val.12, 12);
|
|
||||||
assert_eq!(val.13, 13);
|
|
||||||
assert_eq!(val.14, 14);
|
|
||||||
assert_eq!(val.15, 15);
|
|
||||||
|
|
||||||
// We don't test one bigger because it's unimplemented
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "bundled")]
|
|
||||||
fn pathological_case() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
conn.execute_batch(
|
|
||||||
"CREATE TABLE foo(x);
|
|
||||||
CREATE TRIGGER oops BEFORE INSERT ON foo BEGIN SELECT RAISE(FAIL, 'Boom'); END;",
|
|
||||||
)?;
|
|
||||||
let mut stmt = conn.prepare("INSERT INTO foo VALUES (0) RETURNING rowid;")?;
|
|
||||||
{
|
|
||||||
let iterator_count = stmt.query_map([], |_| Ok(()))?.count();
|
|
||||||
assert_eq!(1, iterator_count); // should be 0
|
|
||||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
|
||||||
let fallible_iterator_count = stmt.query([])?.count().unwrap_or(0);
|
|
||||||
assert_eq!(0, fallible_iterator_count);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let iterator_last = stmt.query_map([], |_| Ok(()))?.last();
|
|
||||||
assert!(iterator_last.is_some()); // should be none
|
|
||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
let fallible_iterator_last = stmt.query([])?.map(|_| Ok(())).last();
|
|
||||||
assert!(fallible_iterator_last.is_err());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn as_ref() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
let mut stmt = conn.prepare("SELECT 'Lisa' as name, 1 as id")?;
|
|
||||||
let rows = stmt.query([])?;
|
|
||||||
assert_eq!(rows.as_ref().unwrap().column_count(), 2);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn debug() -> Result<()> {
|
|
||||||
let conn = Connection::open_in_memory()?;
|
|
||||||
let mut stmt = conn.prepare(
|
|
||||||
"SELECT 'Lisa' as name, 1 as id, 3.14 as pi, X'53514C697465' as blob, NULL as void",
|
|
||||||
)?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
let row = rows.next()?.unwrap();
|
|
||||||
let s = format!("{row:?}");
|
|
||||||
assert_eq!(
|
|
||||||
s,
|
|
||||||
r#"{"name": (Text, "Lisa"), "id": (Integer, 1), "pi": (Real, 3.14), "blob": (Blob, 6), "void": (Null, ())}"#
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "pointer")]
|
|
||||||
fn test_pointer() -> Result<()> {
|
|
||||||
use crate::ffi::fts5_api;
|
|
||||||
use crate::types::ToSqlOutput;
|
|
||||||
const PTR_TYPE: &std::ffi::CStr = c"fts5_api_ptr";
|
|
||||||
let p_ret: *mut fts5_api = std::ptr::null_mut();
|
|
||||||
let ptr = ToSqlOutput::Pointer((&p_ret as *const *mut fts5_api as _, PTR_TYPE, None));
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.query_row("SELECT fts5(?)", [ptr], |_| Ok(()))?;
|
|
||||||
assert!(!p_ret.is_null());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
237
vendor/rusqlite/src/serialize.rs
vendored
237
vendor/rusqlite/src/serialize.rs
vendored
@@ -1,237 +0,0 @@
|
|||||||
//! Serialize a database.
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::ptr::NonNull;
|
|
||||||
|
|
||||||
use crate::error::{error_from_handle, error_from_sqlite_code};
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::{Connection, Error, Name, Result};
|
|
||||||
|
|
||||||
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
|
|
||||||
pub struct SharedData<'conn> {
|
|
||||||
phantom: PhantomData<&'conn Connection>,
|
|
||||||
ptr: NonNull<u8>,
|
|
||||||
sz: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Owned serialized database
|
|
||||||
pub struct OwnedData {
|
|
||||||
ptr: NonNull<u8>,
|
|
||||||
sz: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnedData {
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Caller must be certain that `ptr` is allocated by `sqlite3_malloc64`.
|
|
||||||
pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, sz: usize) -> Self {
|
|
||||||
Self { ptr, sz }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_raw(self) -> (*mut u8, usize) {
|
|
||||||
let raw = (self.ptr.as_ptr(), self.sz);
|
|
||||||
std::mem::forget(self);
|
|
||||||
raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for OwnedData {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_free(self.ptr.as_ptr().cast());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialized database
|
|
||||||
pub enum Data<'conn> {
|
|
||||||
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
|
|
||||||
Shared(SharedData<'conn>),
|
|
||||||
/// Owned serialized database
|
|
||||||
Owned(OwnedData),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Data<'_> {
|
|
||||||
type Target = [u8];
|
|
||||||
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
let (ptr, sz) = match self {
|
|
||||||
Data::Owned(OwnedData { ptr, sz }) => (ptr.as_ptr(), *sz),
|
|
||||||
Data::Shared(SharedData { ptr, sz, .. }) => (ptr.as_ptr(), *sz),
|
|
||||||
};
|
|
||||||
unsafe { std::slice::from_raw_parts(ptr, sz) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Serialize a database.
|
|
||||||
pub fn serialize<N: Name>(&self, schema: N) -> Result<Data<'_>> {
|
|
||||||
let schema = schema.as_cstr()?;
|
|
||||||
let mut sz = 0;
|
|
||||||
let mut ptr: *mut u8 = unsafe {
|
|
||||||
ffi::sqlite3_serialize(
|
|
||||||
self.handle(),
|
|
||||||
schema.as_ptr(),
|
|
||||||
&mut sz,
|
|
||||||
ffi::SQLITE_SERIALIZE_NOCOPY,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
Ok(if ptr.is_null() {
|
|
||||||
ptr = unsafe { ffi::sqlite3_serialize(self.handle(), schema.as_ptr(), &mut sz, 0) };
|
|
||||||
if ptr.is_null() {
|
|
||||||
return Err(unsafe { error_from_handle(self.handle(), ffi::SQLITE_NOMEM) });
|
|
||||||
}
|
|
||||||
Data::Owned(OwnedData {
|
|
||||||
ptr: NonNull::new(ptr).unwrap(),
|
|
||||||
sz: sz.try_into().unwrap(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// shared buffer
|
|
||||||
Data::Shared(SharedData {
|
|
||||||
ptr: NonNull::new(ptr).unwrap(),
|
|
||||||
sz: sz.try_into().unwrap(),
|
|
||||||
phantom: PhantomData,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize from stream
|
|
||||||
pub fn deserialize_read_exact<N: Name, R: std::io::Read>(
|
|
||||||
&mut self,
|
|
||||||
schema: N,
|
|
||||||
mut read: R,
|
|
||||||
sz: usize,
|
|
||||||
read_only: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let ptr = unsafe { ffi::sqlite3_malloc64(sz.try_into().unwrap()) }.cast::<u8>();
|
|
||||||
if ptr.is_null() {
|
|
||||||
return Err(error_from_sqlite_code(ffi::SQLITE_NOMEM, None));
|
|
||||||
}
|
|
||||||
let buf = unsafe { std::slice::from_raw_parts_mut(ptr, sz) };
|
|
||||||
read.read_exact(buf).map_err(|e| {
|
|
||||||
Error::SqliteFailure(
|
|
||||||
ffi::Error {
|
|
||||||
code: ffi::ErrorCode::CannotOpen,
|
|
||||||
extended_code: ffi::SQLITE_IOERR,
|
|
||||||
},
|
|
||||||
Some(format!("{e}")),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let ptr = NonNull::new(ptr).unwrap();
|
|
||||||
let data = unsafe { OwnedData::from_raw_nonnull(ptr, sz) };
|
|
||||||
self.deserialize(schema, data, read_only)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize `include_bytes` as a read only database
|
|
||||||
pub fn deserialize_bytes<N: Name>(&mut self, schema: N, data: &'static [u8]) -> Result<()> {
|
|
||||||
let sz = data.len().try_into().unwrap();
|
|
||||||
self.deserialize_(
|
|
||||||
schema,
|
|
||||||
data.as_ptr() as *mut _,
|
|
||||||
sz,
|
|
||||||
ffi::SQLITE_DESERIALIZE_READONLY,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize a database.
|
|
||||||
pub fn deserialize<N: Name>(
|
|
||||||
&mut self,
|
|
||||||
schema: N,
|
|
||||||
data: OwnedData,
|
|
||||||
read_only: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let (data, sz) = data.into_raw();
|
|
||||||
let sz = sz.try_into().unwrap();
|
|
||||||
let flags = if read_only {
|
|
||||||
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_READONLY
|
|
||||||
} else {
|
|
||||||
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_RESIZEABLE
|
|
||||||
};
|
|
||||||
self.deserialize_(schema, data, sz, flags)
|
|
||||||
/* TODO
|
|
||||||
if let Some(mxSize) = mxSize {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_file_control(
|
|
||||||
self.handle(),
|
|
||||||
schema.as_ptr(),
|
|
||||||
ffi::SQLITE_FCNTL_SIZE_LIMIT,
|
|
||||||
&mut mxSize,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_<N: Name>(
|
|
||||||
&mut self,
|
|
||||||
schema: N,
|
|
||||||
data: *mut u8,
|
|
||||||
sz: ffi::sqlite_int64,
|
|
||||||
flags: std::ffi::c_uint,
|
|
||||||
) -> Result<()> {
|
|
||||||
let schema = schema.as_cstr()?;
|
|
||||||
let rc = unsafe {
|
|
||||||
ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags)
|
|
||||||
};
|
|
||||||
if rc != ffi::SQLITE_OK {
|
|
||||||
return Err(unsafe { error_from_handle(self.handle(), rc) });
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::MAIN_DB;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
|
||||||
let data = db.serialize(MAIN_DB)?;
|
|
||||||
let Data::Owned(data) = data else {
|
|
||||||
panic!("expected OwnedData")
|
|
||||||
};
|
|
||||||
assert!(data.sz > 0);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_read_exact() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
|
||||||
let data = db.serialize(MAIN_DB)?;
|
|
||||||
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
let read = data.deref();
|
|
||||||
dst.deserialize_read_exact(MAIN_DB, read, read.len(), false)?;
|
|
||||||
dst.execute("DELETE FROM x", [])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_bytes() -> Result<()> {
|
|
||||||
let data = b"";
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
dst.deserialize_bytes(MAIN_DB, data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize() -> Result<()> {
|
|
||||||
let src = Connection::open_in_memory()?;
|
|
||||||
src.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
|
||||||
let data = src.serialize(MAIN_DB)?;
|
|
||||||
let Data::Owned(data) = data else {
|
|
||||||
panic!("expected OwnedData")
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut dst = Connection::open_in_memory()?;
|
|
||||||
dst.deserialize(MAIN_DB, data, false)?;
|
|
||||||
dst.execute("DELETE FROM x", [])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
963
vendor/rusqlite/src/session.rs
vendored
963
vendor/rusqlite/src/session.rs
vendored
@@ -1,963 +0,0 @@
|
|||||||
//! [Session Extension](https://sqlite.org/sessionintro.html)
|
|
||||||
#![expect(non_camel_case_types)]
|
|
||||||
|
|
||||||
use std::ffi::{c_char, c_int, c_uchar, c_void, CStr};
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
use std::ptr;
|
|
||||||
use std::slice::{from_raw_parts, from_raw_parts_mut};
|
|
||||||
|
|
||||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
|
||||||
|
|
||||||
use crate::error::{check, error_from_sqlite_code, Error};
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::hooks::Action;
|
|
||||||
use crate::types::ValueRef;
|
|
||||||
use crate::{errmsg_to_string, Connection, Name, Result, MAIN_DB};
|
|
||||||
|
|
||||||
// https://sqlite.org/session.html
|
|
||||||
|
|
||||||
type Filter = Option<Box<dyn Fn(&str) -> bool>>;
|
|
||||||
|
|
||||||
/// An instance of this object is a session that can be
|
|
||||||
/// used to record changes to a database.
|
|
||||||
pub struct Session<'conn> {
|
|
||||||
phantom: PhantomData<&'conn Connection>,
|
|
||||||
s: *mut ffi::sqlite3_session,
|
|
||||||
filter: Filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Session<'_> {
|
|
||||||
/// Create a new session object
|
|
||||||
#[inline]
|
|
||||||
pub fn new(db: &Connection) -> Result<Session<'_>> {
|
|
||||||
Session::new_with_name(db, MAIN_DB)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new session object
|
|
||||||
#[inline]
|
|
||||||
pub fn new_with_name<N: Name>(db: &Connection, name: N) -> Result<Session<'_>> {
|
|
||||||
let name = name.as_cstr()?;
|
|
||||||
|
|
||||||
let db = db.db.borrow_mut().db;
|
|
||||||
|
|
||||||
let mut s: *mut ffi::sqlite3_session = ptr::null_mut();
|
|
||||||
check(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) })?;
|
|
||||||
|
|
||||||
Ok(Session {
|
|
||||||
phantom: PhantomData,
|
|
||||||
s,
|
|
||||||
filter: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a table filter
|
|
||||||
pub fn table_filter<F>(&mut self, filter: Option<F>)
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool + Send + 'static,
|
|
||||||
{
|
|
||||||
unsafe extern "C" fn call_boxed_closure<F>(
|
|
||||||
p_arg: *mut c_void,
|
|
||||||
tbl_str: *const c_char,
|
|
||||||
) -> c_int
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool,
|
|
||||||
{
|
|
||||||
let tbl_name = CStr::from_ptr(tbl_str).to_str();
|
|
||||||
c_int::from(
|
|
||||||
catch_unwind(|| {
|
|
||||||
let boxed_filter: *mut F = p_arg.cast::<F>();
|
|
||||||
(*boxed_filter)(tbl_name.expect("non-utf8 table name"))
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
match filter {
|
|
||||||
Some(filter) => {
|
|
||||||
let boxed_filter = Box::new(filter);
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3session_table_filter(
|
|
||||||
self.s,
|
|
||||||
Some(call_boxed_closure::<F>),
|
|
||||||
&*boxed_filter as *const F as *mut _,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.filter = Some(boxed_filter);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
unsafe { ffi::sqlite3session_table_filter(self.s, None, ptr::null_mut()) }
|
|
||||||
self.filter = None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attach a table. `None` means all tables.
|
|
||||||
pub fn attach<N: Name>(&mut self, table: Option<N>) -> Result<()> {
|
|
||||||
let cs = table.as_ref().map(N::as_cstr).transpose()?;
|
|
||||||
let table = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
|
||||||
check(unsafe { ffi::sqlite3session_attach(self.s, table) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a Changeset
|
|
||||||
pub fn changeset(&mut self) -> Result<Changeset> {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut cs: *mut c_void = ptr::null_mut();
|
|
||||||
check(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) })?;
|
|
||||||
Ok(Changeset { cs, n })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the set of changes represented by this session to `output`.
|
|
||||||
#[inline]
|
|
||||||
pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
|
|
||||||
let output_ref = &output;
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3session_changeset_strm(
|
|
||||||
self.s,
|
|
||||||
Some(x_output),
|
|
||||||
output_ref as *const &mut dyn Write as *mut c_void,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a Patchset
|
|
||||||
#[inline]
|
|
||||||
pub fn patchset(&mut self) -> Result<Changeset> {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut ps: *mut c_void = ptr::null_mut();
|
|
||||||
check(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) })?;
|
|
||||||
// TODO Validate: same struct
|
|
||||||
Ok(Changeset { cs: ps, n })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the set of patches represented by this session to `output`.
|
|
||||||
#[inline]
|
|
||||||
pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
|
|
||||||
let output_ref = &output;
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3session_patchset_strm(
|
|
||||||
self.s,
|
|
||||||
Some(x_output),
|
|
||||||
output_ref as *const &mut dyn Write as *mut c_void,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load the difference between tables.
|
|
||||||
pub fn diff<D: Name, N: Name>(&mut self, from: N, table: N) -> Result<()> {
|
|
||||||
let from = from.as_cstr()?;
|
|
||||||
let table = table.as_cstr()?;
|
|
||||||
let table = table.as_ptr();
|
|
||||||
unsafe {
|
|
||||||
let mut errmsg = ptr::null_mut();
|
|
||||||
let r =
|
|
||||||
ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg as *mut *mut _);
|
|
||||||
if r != ffi::SQLITE_OK {
|
|
||||||
let errmsg: *mut c_char = errmsg;
|
|
||||||
let message = errmsg_to_string(&*errmsg);
|
|
||||||
ffi::sqlite3_free(errmsg as *mut c_void);
|
|
||||||
return Err(error_from_sqlite_code(r, Some(message)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if a changeset has recorded any changes
|
|
||||||
#[inline]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
unsafe { ffi::sqlite3session_isempty(self.s) != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the current state of the session
|
|
||||||
#[inline]
|
|
||||||
pub fn is_enabled(&self) -> bool {
|
|
||||||
unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable or disable the recording of changes
|
|
||||||
#[inline]
|
|
||||||
pub fn set_enabled(&mut self, enabled: bool) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3session_enable(self.s, c_int::from(enabled));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the current state of the indirect flag
|
|
||||||
#[inline]
|
|
||||||
pub fn is_indirect(&self) -> bool {
|
|
||||||
unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set or clear the indirect change flag
|
|
||||||
#[inline]
|
|
||||||
pub fn set_indirect(&mut self, indirect: bool) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3session_indirect(self.s, c_int::from(indirect));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Session<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.filter.is_some() {
|
|
||||||
self.table_filter(None::<fn(&str) -> bool>);
|
|
||||||
}
|
|
||||||
unsafe { ffi::sqlite3session_delete(self.s) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invert a changeset
|
|
||||||
#[inline]
|
|
||||||
pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> {
|
|
||||||
let input_ref = &input;
|
|
||||||
let output_ref = &output;
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changeset_invert_strm(
|
|
||||||
Some(x_input),
|
|
||||||
input_ref as *const &mut dyn Read as *mut c_void,
|
|
||||||
Some(x_output),
|
|
||||||
output_ref as *const &mut dyn Write as *mut c_void,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Combine two changesets
|
|
||||||
#[inline]
|
|
||||||
pub fn concat_strm(
|
|
||||||
input_a: &mut dyn Read,
|
|
||||||
input_b: &mut dyn Read,
|
|
||||||
output: &mut dyn Write,
|
|
||||||
) -> Result<()> {
|
|
||||||
let input_a_ref = &input_a;
|
|
||||||
let input_b_ref = &input_b;
|
|
||||||
let output_ref = &output;
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changeset_concat_strm(
|
|
||||||
Some(x_input),
|
|
||||||
input_a_ref as *const &mut dyn Read as *mut c_void,
|
|
||||||
Some(x_input),
|
|
||||||
input_b_ref as *const &mut dyn Read as *mut c_void,
|
|
||||||
Some(x_output),
|
|
||||||
output_ref as *const &mut dyn Write as *mut c_void,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Changeset or Patchset
|
|
||||||
pub struct Changeset {
|
|
||||||
cs: *mut c_void,
|
|
||||||
n: c_int,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Changeset {
|
|
||||||
/// Invert a changeset
|
|
||||||
#[inline]
|
|
||||||
pub fn invert(&self) -> Result<Changeset> {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut cs = ptr::null_mut();
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs as *mut *mut _)
|
|
||||||
})?;
|
|
||||||
Ok(Changeset { cs, n })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an iterator to traverse a changeset
|
|
||||||
#[inline]
|
|
||||||
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
|
||||||
let mut it = ptr::null_mut();
|
|
||||||
check(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) })?;
|
|
||||||
Ok(ChangesetIter {
|
|
||||||
phantom: PhantomData,
|
|
||||||
it,
|
|
||||||
item: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Concatenate two changeset objects
|
|
||||||
#[inline]
|
|
||||||
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut cs = ptr::null_mut();
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs as *mut *mut _)
|
|
||||||
})?;
|
|
||||||
Ok(Changeset { cs, n })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Changeset {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_free(self.cs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cursor for iterating over the elements of a changeset
|
|
||||||
/// or patchset.
|
|
||||||
pub struct ChangesetIter<'changeset> {
|
|
||||||
phantom: PhantomData<&'changeset Changeset>,
|
|
||||||
it: *mut ffi::sqlite3_changeset_iter,
|
|
||||||
item: Option<ChangesetItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChangesetIter<'_> {
|
|
||||||
/// Create an iterator on `input`
|
|
||||||
#[inline]
|
|
||||||
pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
|
||||||
let mut it = ptr::null_mut();
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changeset_start_strm(
|
|
||||||
&mut it as *mut *mut _,
|
|
||||||
Some(x_input),
|
|
||||||
input as *const &mut dyn Read as *mut c_void,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(ChangesetIter {
|
|
||||||
phantom: PhantomData,
|
|
||||||
it,
|
|
||||||
item: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FallibleStreamingIterator for ChangesetIter<'_> {
|
|
||||||
type Error = Error;
|
|
||||||
type Item = ChangesetItem;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn advance(&mut self) -> Result<()> {
|
|
||||||
let rc = unsafe { ffi::sqlite3changeset_next(self.it) };
|
|
||||||
match rc {
|
|
||||||
ffi::SQLITE_ROW => {
|
|
||||||
self.item = Some(ChangesetItem { it: self.it });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
ffi::SQLITE_DONE => {
|
|
||||||
self.item = None;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
code => Err(error_from_sqlite_code(code, None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get(&self) -> Option<&ChangesetItem> {
|
|
||||||
self.item.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Operation
|
|
||||||
pub struct Operation<'item> {
|
|
||||||
table_name: &'item str,
|
|
||||||
number_of_columns: i32,
|
|
||||||
code: Action,
|
|
||||||
indirect: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Operation<'_> {
|
|
||||||
/// Returns the table name.
|
|
||||||
#[inline]
|
|
||||||
pub fn table_name(&self) -> &str {
|
|
||||||
self.table_name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of columns in table
|
|
||||||
#[inline]
|
|
||||||
pub fn number_of_columns(&self) -> i32 {
|
|
||||||
self.number_of_columns
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the action code.
|
|
||||||
#[inline]
|
|
||||||
pub fn code(&self) -> Action {
|
|
||||||
self.code
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` for an 'indirect' change.
|
|
||||||
#[inline]
|
|
||||||
pub fn indirect(&self) -> bool {
|
|
||||||
self.indirect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ChangesetIter<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3changeset_finalize(self.it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An item passed to a conflict-handler by
|
|
||||||
/// [`Connection::apply`](Connection::apply), or an item generated by
|
|
||||||
/// [`ChangesetIter::next`](ChangesetIter::next).
|
|
||||||
// TODO enum ? Delete, Insert, Update, ...
|
|
||||||
pub struct ChangesetItem {
|
|
||||||
it: *mut ffi::sqlite3_changeset_iter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChangesetItem {
|
|
||||||
/// Obtain conflicting row values
|
|
||||||
///
|
|
||||||
/// May only be called with an `SQLITE_CHANGESET_DATA` or
|
|
||||||
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
|
||||||
#[inline]
|
|
||||||
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
|
||||||
unsafe {
|
|
||||||
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
|
|
||||||
check(ffi::sqlite3changeset_conflict(
|
|
||||||
self.it,
|
|
||||||
col as i32,
|
|
||||||
&mut p_value,
|
|
||||||
))?;
|
|
||||||
if p_value.is_null() {
|
|
||||||
Err(Error::InvalidColumnIndex(col))
|
|
||||||
} else {
|
|
||||||
Ok(ValueRef::from_value(p_value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the number of foreign key constraint violations
|
|
||||||
///
|
|
||||||
/// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict
|
|
||||||
/// handler callback.
|
|
||||||
#[inline]
|
|
||||||
pub fn fk_conflicts(&self) -> Result<i32> {
|
|
||||||
unsafe {
|
|
||||||
let mut p_out = 0;
|
|
||||||
check(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out))?;
|
|
||||||
Ok(p_out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain new.* Values
|
|
||||||
///
|
|
||||||
/// May only be called if the type of change is either `SQLITE_UPDATE` or
|
|
||||||
/// `SQLITE_INSERT`.
|
|
||||||
#[inline]
|
|
||||||
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
|
||||||
unsafe {
|
|
||||||
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
|
|
||||||
check(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value))?;
|
|
||||||
if p_value.is_null() {
|
|
||||||
Err(Error::InvalidColumnIndex(col))
|
|
||||||
} else {
|
|
||||||
Ok(ValueRef::from_value(p_value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain old.* Values
|
|
||||||
///
|
|
||||||
/// May only be called if the type of change is either `SQLITE_DELETE` or
|
|
||||||
/// `SQLITE_UPDATE`.
|
|
||||||
#[inline]
|
|
||||||
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
|
||||||
unsafe {
|
|
||||||
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
|
|
||||||
check(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value))?;
|
|
||||||
if p_value.is_null() {
|
|
||||||
Err(Error::InvalidColumnIndex(col))
|
|
||||||
} else {
|
|
||||||
Ok(ValueRef::from_value(p_value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain the current operation
|
|
||||||
#[inline]
|
|
||||||
pub fn op(&self) -> Result<Operation<'_>> {
|
|
||||||
let mut number_of_columns = 0;
|
|
||||||
let mut code = 0;
|
|
||||||
let mut indirect = 0;
|
|
||||||
let tab = unsafe {
|
|
||||||
let mut pz_tab: *const c_char = ptr::null();
|
|
||||||
check(ffi::sqlite3changeset_op(
|
|
||||||
self.it,
|
|
||||||
&mut pz_tab,
|
|
||||||
&mut number_of_columns,
|
|
||||||
&mut code,
|
|
||||||
&mut indirect,
|
|
||||||
))?;
|
|
||||||
CStr::from_ptr(pz_tab)
|
|
||||||
};
|
|
||||||
let table_name = tab.to_str()?;
|
|
||||||
Ok(Operation {
|
|
||||||
table_name,
|
|
||||||
number_of_columns,
|
|
||||||
code: Action::from(code),
|
|
||||||
indirect: indirect != 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain the primary key definition of a table
|
|
||||||
#[inline]
|
|
||||||
pub fn pk(&self) -> Result<&[u8]> {
|
|
||||||
let mut number_of_columns = 0;
|
|
||||||
unsafe {
|
|
||||||
let mut pks: *mut c_uchar = ptr::null_mut();
|
|
||||||
check(ffi::sqlite3changeset_pk(
|
|
||||||
self.it,
|
|
||||||
&mut pks,
|
|
||||||
&mut number_of_columns,
|
|
||||||
))?;
|
|
||||||
Ok(from_raw_parts(pks, number_of_columns as usize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to combine two or more changesets or
|
|
||||||
/// patchsets
|
|
||||||
pub struct Changegroup {
|
|
||||||
cg: *mut ffi::sqlite3_changegroup,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Changegroup {
|
|
||||||
/// Create a new change group.
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
let mut cg = ptr::null_mut();
|
|
||||||
check(unsafe { ffi::sqlite3changegroup_new(&mut cg) })?;
|
|
||||||
Ok(Changegroup { cg })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a changeset
|
|
||||||
#[inline]
|
|
||||||
pub fn add(&mut self, cs: &Changeset) -> Result<()> {
|
|
||||||
check(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a changeset read from `input` to this change group.
|
|
||||||
#[inline]
|
|
||||||
pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> {
|
|
||||||
let input_ref = &input;
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changegroup_add_strm(
|
|
||||||
self.cg,
|
|
||||||
Some(x_input),
|
|
||||||
input_ref as *const &mut dyn Read as *mut c_void,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain a composite Changeset
|
|
||||||
#[inline]
|
|
||||||
pub fn output(&mut self) -> Result<Changeset> {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut output: *mut c_void = ptr::null_mut();
|
|
||||||
check(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) })?;
|
|
||||||
Ok(Changeset { cs: output, n })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the combined set of changes to `output`.
|
|
||||||
#[inline]
|
|
||||||
pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> {
|
|
||||||
let output_ref = &output;
|
|
||||||
check(unsafe {
|
|
||||||
ffi::sqlite3changegroup_output_strm(
|
|
||||||
self.cg,
|
|
||||||
Some(x_output),
|
|
||||||
output_ref as *const &mut dyn Write as *mut c_void,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Changegroup {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3changegroup_delete(self.cg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Apply a changeset to a database
|
|
||||||
pub fn apply<F, C>(&self, cs: &Changeset, filter: Option<F>, conflict: C) -> Result<()>
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool + Send + 'static,
|
|
||||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static,
|
|
||||||
{
|
|
||||||
let db = self.db.borrow_mut().db;
|
|
||||||
|
|
||||||
let filtered = filter.is_some();
|
|
||||||
let tuple = &mut (filter, conflict);
|
|
||||||
check(unsafe {
|
|
||||||
if filtered {
|
|
||||||
ffi::sqlite3changeset_apply(
|
|
||||||
db,
|
|
||||||
cs.n,
|
|
||||||
cs.cs,
|
|
||||||
Some(call_filter::<F, C>),
|
|
||||||
Some(call_conflict::<F, C>),
|
|
||||||
tuple as *mut (Option<F>, C) as *mut c_void,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ffi::sqlite3changeset_apply(
|
|
||||||
db,
|
|
||||||
cs.n,
|
|
||||||
cs.cs,
|
|
||||||
None,
|
|
||||||
Some(call_conflict::<F, C>),
|
|
||||||
tuple as *mut (Option<F>, C) as *mut c_void,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply a changeset to a database
|
|
||||||
pub fn apply_strm<F, C>(
|
|
||||||
&self,
|
|
||||||
input: &mut dyn Read,
|
|
||||||
filter: Option<F>,
|
|
||||||
conflict: C,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool + Send + 'static,
|
|
||||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static,
|
|
||||||
{
|
|
||||||
let input_ref = &input;
|
|
||||||
let db = self.db.borrow_mut().db;
|
|
||||||
|
|
||||||
let filtered = filter.is_some();
|
|
||||||
let tuple = &mut (filter, conflict);
|
|
||||||
check(unsafe {
|
|
||||||
if filtered {
|
|
||||||
ffi::sqlite3changeset_apply_strm(
|
|
||||||
db,
|
|
||||||
Some(x_input),
|
|
||||||
input_ref as *const &mut dyn Read as *mut c_void,
|
|
||||||
Some(call_filter::<F, C>),
|
|
||||||
Some(call_conflict::<F, C>),
|
|
||||||
tuple as *mut (Option<F>, C) as *mut c_void,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ffi::sqlite3changeset_apply_strm(
|
|
||||||
db,
|
|
||||||
Some(x_input),
|
|
||||||
input_ref as *const &mut dyn Read as *mut c_void,
|
|
||||||
None,
|
|
||||||
Some(call_conflict::<F, C>),
|
|
||||||
tuple as *mut (Option<F>, C) as *mut c_void,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constants passed to the conflict handler
|
|
||||||
/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[repr(i32)]
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ConflictType {
|
|
||||||
UNKNOWN = -1,
|
|
||||||
SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA,
|
|
||||||
SQLITE_CHANGESET_NOTFOUND = ffi::SQLITE_CHANGESET_NOTFOUND,
|
|
||||||
SQLITE_CHANGESET_CONFLICT = ffi::SQLITE_CHANGESET_CONFLICT,
|
|
||||||
SQLITE_CHANGESET_CONSTRAINT = ffi::SQLITE_CHANGESET_CONSTRAINT,
|
|
||||||
SQLITE_CHANGESET_FOREIGN_KEY = ffi::SQLITE_CHANGESET_FOREIGN_KEY,
|
|
||||||
}
|
|
||||||
impl From<i32> for ConflictType {
|
|
||||||
fn from(code: i32) -> ConflictType {
|
|
||||||
match code {
|
|
||||||
ffi::SQLITE_CHANGESET_DATA => ConflictType::SQLITE_CHANGESET_DATA,
|
|
||||||
ffi::SQLITE_CHANGESET_NOTFOUND => ConflictType::SQLITE_CHANGESET_NOTFOUND,
|
|
||||||
ffi::SQLITE_CHANGESET_CONFLICT => ConflictType::SQLITE_CHANGESET_CONFLICT,
|
|
||||||
ffi::SQLITE_CHANGESET_CONSTRAINT => ConflictType::SQLITE_CHANGESET_CONSTRAINT,
|
|
||||||
ffi::SQLITE_CHANGESET_FOREIGN_KEY => ConflictType::SQLITE_CHANGESET_FOREIGN_KEY,
|
|
||||||
_ => ConflictType::UNKNOWN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constants returned by the conflict handler
|
|
||||||
/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[repr(i32)]
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ConflictAction {
|
|
||||||
SQLITE_CHANGESET_OMIT = ffi::SQLITE_CHANGESET_OMIT,
|
|
||||||
SQLITE_CHANGESET_REPLACE = ffi::SQLITE_CHANGESET_REPLACE,
|
|
||||||
SQLITE_CHANGESET_ABORT = ffi::SQLITE_CHANGESET_ABORT,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn call_filter<F, C>(p_ctx: *mut c_void, tbl_str: *const c_char) -> c_int
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool + Send + 'static,
|
|
||||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static,
|
|
||||||
{
|
|
||||||
let tbl_name = CStr::from_ptr(tbl_str).to_str();
|
|
||||||
c_int::from(
|
|
||||||
catch_unwind(|| {
|
|
||||||
let tuple: *mut (Option<F>, C) = p_ctx.cast::<(Option<F>, C)>();
|
|
||||||
if let Some(ref filter) = (*tuple).0 {
|
|
||||||
filter(tbl_name.expect("illegal table name"))
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn call_conflict<F, C>(
|
|
||||||
p_ctx: *mut c_void,
|
|
||||||
e_conflict: c_int,
|
|
||||||
p: *mut ffi::sqlite3_changeset_iter,
|
|
||||||
) -> c_int
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool + Send + 'static,
|
|
||||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + 'static,
|
|
||||||
{
|
|
||||||
let conflict_type = ConflictType::from(e_conflict);
|
|
||||||
let item = ChangesetItem { it: p };
|
|
||||||
if let Ok(action) = catch_unwind(|| {
|
|
||||||
let tuple: *mut (Option<F>, C) = p_ctx.cast::<(Option<F>, C)>();
|
|
||||||
(*tuple).1(conflict_type, item)
|
|
||||||
}) {
|
|
||||||
action as c_int
|
|
||||||
} else {
|
|
||||||
ffi::SQLITE_CHANGESET_ABORT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_int) -> c_int {
|
|
||||||
if p_in.is_null() {
|
|
||||||
return ffi::SQLITE_MISUSE;
|
|
||||||
}
|
|
||||||
let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, *len as usize);
|
|
||||||
let input = p_in as *mut &mut dyn Read;
|
|
||||||
match (*input).read(bytes) {
|
|
||||||
Ok(n) => {
|
|
||||||
*len = n as i32; // TODO Validate: n = 0 may not mean the reader will always no longer be able to
|
|
||||||
// produce bytes.
|
|
||||||
ffi::SQLITE_OK
|
|
||||||
}
|
|
||||||
Err(_) => ffi::SQLITE_IOERR_READ, // TODO check if err is a (ru)sqlite Error => propagate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_int) -> c_int {
|
|
||||||
if p_out.is_null() {
|
|
||||||
return ffi::SQLITE_MISUSE;
|
|
||||||
}
|
|
||||||
// The sessions module never invokes an xOutput callback with the third
|
|
||||||
// parameter set to a value less than or equal to zero.
|
|
||||||
let bytes: &[u8] = from_raw_parts(data as *const u8, len as usize);
|
|
||||||
let output = p_out as *mut &mut dyn Write;
|
|
||||||
match (*output).write_all(bytes) {
|
|
||||||
Ok(_) => ffi::SQLITE_OK,
|
|
||||||
Err(_) => ffi::SQLITE_IOERR_WRITE, // TODO check if err is a (ru)sqlite Error => propagate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
|
|
||||||
use crate::hooks::Action;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
fn one_changeset_insert() -> Result<Changeset> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
|
|
||||||
|
|
||||||
let mut session = Session::new(&db)?;
|
|
||||||
assert!(session.is_empty());
|
|
||||||
|
|
||||||
session.attach::<&str>(None)?;
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
|
|
||||||
|
|
||||||
session.changeset()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn one_changeset_update() -> Result<Changeset> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch(
|
|
||||||
"CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL, i INTEGER NOT NULL DEFAULT 0);",
|
|
||||||
)?;
|
|
||||||
db.execute_batch("INSERT INTO foo (t) VALUES ('bar');")?;
|
|
||||||
|
|
||||||
let mut session = Session::new(&db)?;
|
|
||||||
session.attach::<&str>(None)?;
|
|
||||||
db.execute("UPDATE foo SET i=100 WHERE t='bar';", [])?;
|
|
||||||
|
|
||||||
session.changeset()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn one_changeset_strm() -> Result<Vec<u8>> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
|
|
||||||
|
|
||||||
let mut session = Session::new(&db)?;
|
|
||||||
assert!(session.is_empty());
|
|
||||||
|
|
||||||
session.attach::<&str>(None)?;
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
|
|
||||||
|
|
||||||
let mut output = Vec::new();
|
|
||||||
session.changeset_strm(&mut output)?;
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_changeset() -> Result<()> {
|
|
||||||
let changeset = one_changeset_insert()?;
|
|
||||||
let mut iter = changeset.iter()?;
|
|
||||||
let item = iter.next()?;
|
|
||||||
assert!(item.is_some());
|
|
||||||
|
|
||||||
let item = item.unwrap();
|
|
||||||
let op = item.op()?;
|
|
||||||
assert_eq!("foo", op.table_name());
|
|
||||||
assert_eq!(1, op.number_of_columns());
|
|
||||||
assert_eq!(Action::SQLITE_INSERT, op.code());
|
|
||||||
assert!(!op.indirect());
|
|
||||||
|
|
||||||
let pk = item.pk()?;
|
|
||||||
assert_eq!(&[1], pk);
|
|
||||||
|
|
||||||
let new_value = item.new_value(0)?;
|
|
||||||
assert_eq!(Ok("bar"), new_value.as_str());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_changeset_strm() -> Result<()> {
|
|
||||||
let output = one_changeset_strm()?;
|
|
||||||
assert!(!output.is_empty());
|
|
||||||
assert_eq!(14, output.len());
|
|
||||||
|
|
||||||
let input: &mut dyn Read = &mut output.as_slice();
|
|
||||||
let mut iter = ChangesetIter::start_strm(&input)?;
|
|
||||||
let item = iter.next()?;
|
|
||||||
assert!(item.is_some());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_changeset_values() -> Result<()> {
|
|
||||||
let changeset = one_changeset_update()?;
|
|
||||||
let mut iter = changeset.iter()?;
|
|
||||||
let item = iter.next()?.unwrap();
|
|
||||||
|
|
||||||
let new_value = item.new_value(0); // unchanged
|
|
||||||
assert_eq!(Err(crate::Error::InvalidColumnIndex(0)), new_value);
|
|
||||||
let new_value = item.new_value(1)?; // updated
|
|
||||||
assert_eq!(Ok(100), new_value.as_i64());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_changeset_apply() -> Result<()> {
|
|
||||||
let changeset = one_changeset_insert()?;
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
|
|
||||||
|
|
||||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
|
||||||
db.apply(
|
|
||||||
&changeset,
|
|
||||||
None::<fn(&str) -> bool>,
|
|
||||||
|_conflict_type, _item| {
|
|
||||||
CALLED.store(true, Ordering::Relaxed);
|
|
||||||
ConflictAction::SQLITE_CHANGESET_OMIT
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert!(!CALLED.load(Ordering::Relaxed));
|
|
||||||
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
|
|
||||||
row.get::<_, i32>(0)
|
|
||||||
})?;
|
|
||||||
assert_eq!(1, check);
|
|
||||||
|
|
||||||
// conflict expected when same changeset applied again on the same db
|
|
||||||
db.apply(
|
|
||||||
&changeset,
|
|
||||||
None::<fn(&str) -> bool>,
|
|
||||||
|conflict_type, item| {
|
|
||||||
CALLED.store(true, Ordering::Relaxed);
|
|
||||||
assert_eq!(ConflictType::SQLITE_CHANGESET_CONFLICT, conflict_type);
|
|
||||||
let conflict = item.conflict(0).unwrap();
|
|
||||||
assert_eq!(Ok("bar"), conflict.as_str());
|
|
||||||
ConflictAction::SQLITE_CHANGESET_OMIT
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
assert!(CALLED.load(Ordering::Relaxed));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_changeset_apply_strm() -> Result<()> {
|
|
||||||
let output = one_changeset_strm()?;
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
|
|
||||||
|
|
||||||
let mut input = output.as_slice();
|
|
||||||
db.apply_strm(
|
|
||||||
&mut input,
|
|
||||||
None::<fn(&str) -> bool>,
|
|
||||||
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
|
|
||||||
row.get::<_, i32>(0)
|
|
||||||
})?;
|
|
||||||
assert_eq!(1, check);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_session_empty() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
|
|
||||||
|
|
||||||
let mut session = Session::new(&db)?;
|
|
||||||
assert!(session.is_empty());
|
|
||||||
|
|
||||||
session.attach::<&str>(None)?;
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
|
|
||||||
|
|
||||||
assert!(!session.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_session_set_enabled() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
let mut session = Session::new(&db)?;
|
|
||||||
assert!(session.is_enabled());
|
|
||||||
session.set_enabled(false);
|
|
||||||
assert!(!session.is_enabled());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_session_set_indirect() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
let mut session = Session::new(&db)?;
|
|
||||||
assert!(!session.is_indirect());
|
|
||||||
session.set_indirect(true);
|
|
||||||
assert!(session.is_indirect());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1396
vendor/rusqlite/src/statement.rs
vendored
1396
vendor/rusqlite/src/statement.rs
vendored
File diff suppressed because it is too large
Load Diff
385
vendor/rusqlite/src/trace.rs
vendored
385
vendor/rusqlite/src/trace.rs
vendored
@@ -1,385 +0,0 @@
|
|||||||
//! Tracing and profiling functions. Error and warning log.
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::mem;
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
use std::ptr;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use super::ffi;
|
|
||||||
use crate::{Connection, StatementStatus, MAIN_DB};
|
|
||||||
|
|
||||||
/// Set up the process-wide SQLite error logging callback.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This function is marked unsafe for two reasons:
|
|
||||||
///
|
|
||||||
/// * The function is not threadsafe. No other SQLite calls may be made while
|
|
||||||
/// `config_log` is running, and multiple threads may not call `config_log`
|
|
||||||
/// simultaneously.
|
|
||||||
/// * The provided `callback` itself function has two requirements:
|
|
||||||
/// * It must not invoke any SQLite calls.
|
|
||||||
/// * It must be threadsafe if SQLite is used in a multithreaded way.
|
|
||||||
///
|
|
||||||
/// cf [The Error And Warning Log](http://sqlite.org/errlog.html).
|
|
||||||
#[cfg(not(feature = "loadable_extension"))]
|
|
||||||
pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> crate::Result<()> {
|
|
||||||
extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
|
|
||||||
let s = unsafe { CStr::from_ptr(msg).to_string_lossy() };
|
|
||||||
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
|
|
||||||
|
|
||||||
drop(catch_unwind(|| callback(err, &s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let rc = if let Some(f) = callback {
|
|
||||||
ffi::sqlite3_config(
|
|
||||||
ffi::SQLITE_CONFIG_LOG,
|
|
||||||
log_callback as extern "C" fn(_, _, _),
|
|
||||||
f as *mut c_void,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let nullptr: *mut c_void = ptr::null_mut();
|
|
||||||
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
|
|
||||||
};
|
|
||||||
|
|
||||||
if rc == ffi::SQLITE_OK {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(crate::error::error_from_sqlite_code(rc, None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a message into the error log established by
|
|
||||||
/// `config_log`.
|
|
||||||
#[inline]
|
|
||||||
pub fn log(err_code: c_int, msg: &str) {
|
|
||||||
let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_log(err_code, b"%s\0" as *const _ as *const c_char, msg.as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags::bitflags! {
|
|
||||||
/// Trace event codes
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct TraceEventCodes: c_uint {
|
|
||||||
/// when a prepared statement first begins running and possibly at other times during the execution
|
|
||||||
/// of the prepared statement, such as at the start of each trigger subprogram
|
|
||||||
const SQLITE_TRACE_STMT = ffi::SQLITE_TRACE_STMT;
|
|
||||||
/// when the statement finishes
|
|
||||||
const SQLITE_TRACE_PROFILE = ffi::SQLITE_TRACE_PROFILE;
|
|
||||||
/// whenever a prepared statement generates a single row of result
|
|
||||||
const SQLITE_TRACE_ROW = ffi::SQLITE_TRACE_ROW;
|
|
||||||
/// when a database connection closes
|
|
||||||
const SQLITE_TRACE_CLOSE = ffi::SQLITE_TRACE_CLOSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trace event
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum TraceEvent<'s> {
|
|
||||||
/// when a prepared statement first begins running and possibly at other times during the execution
|
|
||||||
/// of the prepared statement, such as at the start of each trigger subprogram
|
|
||||||
Stmt(StmtRef<'s>, &'s str),
|
|
||||||
/// when the statement finishes
|
|
||||||
Profile(StmtRef<'s>, Duration),
|
|
||||||
/// whenever a prepared statement generates a single row of result
|
|
||||||
Row(StmtRef<'s>),
|
|
||||||
/// when a database connection closes
|
|
||||||
Close(ConnRef<'s>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Statement reference
|
|
||||||
pub struct StmtRef<'s> {
|
|
||||||
ptr: *mut ffi::sqlite3_stmt,
|
|
||||||
phantom: PhantomData<&'s ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StmtRef<'_> {
|
|
||||||
fn new(ptr: *mut ffi::sqlite3_stmt) -> Self {
|
|
||||||
StmtRef {
|
|
||||||
ptr,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// SQL text
|
|
||||||
pub fn sql(&self) -> Cow<'_, str> {
|
|
||||||
let sql = unsafe { ffi::sqlite3_sql(self.ptr) };
|
|
||||||
|
|
||||||
if sql.is_null() {
|
|
||||||
return Cow::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safety: sql is a valid pointer to a cstr returned by sqlite3
|
|
||||||
unsafe { CStr::from_ptr(sql).to_string_lossy() }
|
|
||||||
}
|
|
||||||
/// Expanded SQL text
|
|
||||||
pub fn expanded_sql(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
crate::raw_statement::expanded_sql(self.ptr).map(|s| s.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Get the value for one of the status counters for this statement.
|
|
||||||
pub fn get_status(&self, status: StatementStatus) -> i32 {
|
|
||||||
unsafe { crate::raw_statement::stmt_status(self.ptr, status, false) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connection reference
|
|
||||||
pub struct ConnRef<'s> {
|
|
||||||
ptr: *mut ffi::sqlite3,
|
|
||||||
phantom: PhantomData<&'s ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnRef<'_> {
|
|
||||||
/// Test for auto-commit mode.
|
|
||||||
pub fn is_autocommit(&self) -> bool {
|
|
||||||
unsafe { crate::inner_connection::get_autocommit(self.ptr) }
|
|
||||||
}
|
|
||||||
/// the path to the database file, if one exists and is known.
|
|
||||||
pub fn db_filename(&self) -> Option<&str> {
|
|
||||||
unsafe { crate::inner_connection::db_filename(self.phantom, self.ptr, MAIN_DB) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Register or clear a callback function that can be
|
|
||||||
/// used for tracing the execution of SQL statements.
|
|
||||||
///
|
|
||||||
/// Prepared statement placeholders are replaced/logged with their assigned
|
|
||||||
/// values. There can only be a single tracer defined for each database
|
|
||||||
/// connection. Setting a new tracer clears the old one.
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
#[deprecated(since = "0.33.0", note = "use trace_v2 instead")]
|
|
||||||
pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
|
|
||||||
unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
|
|
||||||
let trace_fn: fn(&str) = mem::transmute(p_arg);
|
|
||||||
let s = CStr::from_ptr(z_sql).to_string_lossy();
|
|
||||||
drop(catch_unwind(|| trace_fn(&s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_trace(
|
|
||||||
c.db(),
|
|
||||||
trace_fn.as_ref().map(|_| trace_callback as _),
|
|
||||||
trace_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register or clear a callback function that can be
|
|
||||||
/// used for profiling the execution of SQL statements.
|
|
||||||
///
|
|
||||||
/// There can only be a single profiler defined for each database
|
|
||||||
/// connection. Setting a new profiler clears the old one.
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
#[deprecated(since = "0.33.0", note = "use trace_v2 instead")]
|
|
||||||
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
|
|
||||||
unsafe extern "C" fn profile_callback(
|
|
||||||
p_arg: *mut c_void,
|
|
||||||
z_sql: *const c_char,
|
|
||||||
nanoseconds: u64,
|
|
||||||
) {
|
|
||||||
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
|
|
||||||
let s = CStr::from_ptr(z_sql).to_string_lossy();
|
|
||||||
|
|
||||||
let duration = Duration::from_nanos(nanoseconds);
|
|
||||||
drop(catch_unwind(|| profile_fn(&s, duration)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_profile(
|
|
||||||
c.db(),
|
|
||||||
profile_fn.as_ref().map(|_| profile_callback as _),
|
|
||||||
profile_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register or clear a trace callback function
|
|
||||||
pub fn trace_v2(&self, mask: TraceEventCodes, trace_fn: Option<fn(TraceEvent<'_>)>) {
|
|
||||||
unsafe extern "C" fn trace_callback(
|
|
||||||
evt: c_uint,
|
|
||||||
ctx: *mut c_void,
|
|
||||||
p: *mut c_void,
|
|
||||||
x: *mut c_void,
|
|
||||||
) -> c_int {
|
|
||||||
let trace_fn: fn(TraceEvent<'_>) = mem::transmute(ctx);
|
|
||||||
drop(catch_unwind(|| match evt {
|
|
||||||
ffi::SQLITE_TRACE_STMT => {
|
|
||||||
let str = CStr::from_ptr(x as *const c_char).to_string_lossy();
|
|
||||||
trace_fn(TraceEvent::Stmt(
|
|
||||||
StmtRef::new(p as *mut ffi::sqlite3_stmt),
|
|
||||||
&str,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
ffi::SQLITE_TRACE_PROFILE => {
|
|
||||||
let ns = *(x as *const i64);
|
|
||||||
trace_fn(TraceEvent::Profile(
|
|
||||||
StmtRef::new(p as *mut ffi::sqlite3_stmt),
|
|
||||||
Duration::from_nanos(u64::try_from(ns).unwrap_or_default()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
ffi::SQLITE_TRACE_ROW => {
|
|
||||||
trace_fn(TraceEvent::Row(StmtRef::new(p as *mut ffi::sqlite3_stmt)))
|
|
||||||
}
|
|
||||||
ffi::SQLITE_TRACE_CLOSE => trace_fn(TraceEvent::Close(ConnRef {
|
|
||||||
ptr: p as *mut ffi::sqlite3,
|
|
||||||
phantom: PhantomData,
|
|
||||||
})),
|
|
||||||
_ => {}
|
|
||||||
}));
|
|
||||||
// The integer return value from the callback is currently ignored, though this may change in future releases.
|
|
||||||
// Callback implementations should return zero to ensure future compatibility.
|
|
||||||
ffi::SQLITE_OK
|
|
||||||
}
|
|
||||||
let c = self.db.borrow_mut();
|
|
||||||
unsafe {
|
|
||||||
ffi::sqlite3_trace_v2(
|
|
||||||
c.db(),
|
|
||||||
mask.bits(),
|
|
||||||
trace_fn.as_ref().map(|_| trace_callback as _),
|
|
||||||
trace_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
use std::sync::{LazyLock, Mutex};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use super::{TraceEvent, TraceEventCodes};
|
|
||||||
use crate::{Connection, Result, MAIN_DB};
|
|
||||||
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
#[test]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
fn test_trace() -> Result<()> {
|
|
||||||
static TRACED_STMTS: LazyLock<Mutex<Vec<String>>> =
|
|
||||||
LazyLock::new(|| Mutex::new(Vec::new()));
|
|
||||||
fn tracer(s: &str) {
|
|
||||||
let mut traced_stmts = TRACED_STMTS.lock().unwrap();
|
|
||||||
traced_stmts.push(s.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut db = Connection::open_in_memory()?;
|
|
||||||
db.trace(Some(tracer));
|
|
||||||
{
|
|
||||||
let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
|
|
||||||
let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
|
|
||||||
}
|
|
||||||
db.trace(None);
|
|
||||||
{
|
|
||||||
let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
|
|
||||||
let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let traced_stmts = TRACED_STMTS.lock().unwrap();
|
|
||||||
assert_eq!(traced_stmts.len(), 2);
|
|
||||||
assert_eq!(traced_stmts[0], "SELECT 1");
|
|
||||||
assert_eq!(traced_stmts[1], "SELECT 'hello'");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
#[test]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
fn test_profile() -> Result<()> {
|
|
||||||
static PROFILED: LazyLock<Mutex<Vec<(String, Duration)>>> =
|
|
||||||
LazyLock::new(|| Mutex::new(Vec::new()));
|
|
||||||
fn profiler(s: &str, d: Duration) {
|
|
||||||
let mut profiled = PROFILED.lock().unwrap();
|
|
||||||
profiled.push((s.to_owned(), d));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut db = Connection::open_in_memory()?;
|
|
||||||
db.profile(Some(profiler));
|
|
||||||
db.execute_batch("PRAGMA application_id = 1")?;
|
|
||||||
db.profile(None);
|
|
||||||
db.execute_batch("PRAGMA application_id = 2")?;
|
|
||||||
|
|
||||||
let profiled = PROFILED.lock().unwrap();
|
|
||||||
assert_eq!(profiled.len(), 1);
|
|
||||||
assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn trace_v2() -> Result<()> {
|
|
||||||
use std::borrow::Borrow;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.trace_v2(
|
|
||||||
TraceEventCodes::all(),
|
|
||||||
Some(|e| match e {
|
|
||||||
TraceEvent::Stmt(s, sql) => {
|
|
||||||
assert_eq!(s.sql(), sql);
|
|
||||||
}
|
|
||||||
TraceEvent::Profile(s, d) => {
|
|
||||||
assert_eq!(s.get_status(crate::StatementStatus::Sort), 0);
|
|
||||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
|
||||||
assert_eq!(d.cmp(&Duration::ZERO), Ordering::Greater);
|
|
||||||
// Timers on the web are not very accurate
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
assert!(matches!(
|
|
||||||
d.cmp(&Duration::ZERO),
|
|
||||||
Ordering::Equal | Ordering::Greater
|
|
||||||
));
|
|
||||||
}
|
|
||||||
TraceEvent::Row(s) => {
|
|
||||||
assert_eq!(s.expanded_sql().as_deref(), Some(s.sql().borrow()));
|
|
||||||
}
|
|
||||||
TraceEvent::Close(db) => {
|
|
||||||
assert!(db.is_autocommit());
|
|
||||||
// https://www.sqlite.org/c3ref/db_filename.html
|
|
||||||
// if database N is a temporary or in-memory database,
|
|
||||||
// then this function will return either a NULL pointer or an empty string.
|
|
||||||
assert!(db.db_filename().is_none_or(|s| s.is_empty()));
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
db.one_column::<u32, _>("PRAGMA user_version", [])?;
|
|
||||||
drop(db);
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.trace_v2(TraceEventCodes::empty(), None);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
pub fn null_sql() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
let sql = "CREATE TABLE test (content BLOB);
|
|
||||||
INSERT INTO test VALUES (ZEROBLOB(10));";
|
|
||||||
db.execute_batch(sql)?;
|
|
||||||
let rowid = db.last_insert_rowid();
|
|
||||||
|
|
||||||
db.trace_v2(
|
|
||||||
TraceEventCodes::SQLITE_TRACE_ROW,
|
|
||||||
Some(|e| {
|
|
||||||
if let TraceEvent::Row(s) = e {
|
|
||||||
assert_eq!(s.sql(), "");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
db.blob_open(MAIN_DB, c"test", c"content", rowid, true)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
826
vendor/rusqlite/src/transaction.rs
vendored
826
vendor/rusqlite/src/transaction.rs
vendored
@@ -1,826 +0,0 @@
|
|||||||
use crate::{Connection, Result};
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
/// Options for transaction behavior. See [BEGIN
|
|
||||||
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum TransactionBehavior {
|
|
||||||
/// DEFERRED means that the transaction does not actually start until the
|
|
||||||
/// database is first accessed.
|
|
||||||
Deferred,
|
|
||||||
/// IMMEDIATE cause the database connection to start a new write
|
|
||||||
/// immediately, without waiting for a writes statement.
|
|
||||||
Immediate,
|
|
||||||
/// EXCLUSIVE prevents other database connections from reading the database
|
|
||||||
/// while the transaction is underway.
|
|
||||||
Exclusive,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Options for how a Transaction or Savepoint should behave when it is dropped.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum DropBehavior {
|
|
||||||
/// Roll back the changes. This is the default.
|
|
||||||
Rollback,
|
|
||||||
|
|
||||||
/// Commit the changes.
|
|
||||||
Commit,
|
|
||||||
|
|
||||||
/// Do not commit or roll back changes - this will leave the transaction or
|
|
||||||
/// savepoint open, so should be used with care.
|
|
||||||
Ignore,
|
|
||||||
|
|
||||||
/// Panic. Used to enforce intentional behavior during development.
|
|
||||||
Panic,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a transaction on a database connection.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// Transactions will roll back by default. Use `commit` method to explicitly
|
|
||||||
/// commit the transaction, or use `set_drop_behavior` to change what happens
|
|
||||||
/// when the transaction is dropped.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
|
||||||
/// let tx = conn.transaction()?;
|
|
||||||
///
|
|
||||||
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
||||||
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
|
||||||
///
|
|
||||||
/// tx.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Transaction<'conn> {
|
|
||||||
conn: &'conn Connection,
|
|
||||||
drop_behavior: DropBehavior,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a savepoint on a database connection.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// Savepoints will roll back by default. Use `commit` method to explicitly
|
|
||||||
/// commit the savepoint, or use `set_drop_behavior` to change what happens
|
|
||||||
/// when the savepoint is dropped.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
|
||||||
/// let sp = conn.savepoint()?;
|
|
||||||
///
|
|
||||||
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
|
|
||||||
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
|
|
||||||
///
|
|
||||||
/// sp.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Savepoint<'conn> {
|
|
||||||
conn: &'conn Connection,
|
|
||||||
name: String,
|
|
||||||
drop_behavior: DropBehavior,
|
|
||||||
committed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transaction<'_> {
|
|
||||||
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
|
||||||
/// transactions.
|
|
||||||
///
|
|
||||||
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
|
||||||
/// to prevent nested transactions on the same connection. For cases
|
|
||||||
/// where this is unacceptable, [`Transaction::new_unchecked`] is available.
|
|
||||||
#[inline]
|
|
||||||
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
|
|
||||||
Self::new_unchecked(conn, behavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new transaction, failing if a transaction is open.
|
|
||||||
///
|
|
||||||
/// If a transaction is already open, this will return an error. Where
|
|
||||||
/// possible, [`Transaction::new`] should be preferred, as it provides a
|
|
||||||
/// compile-time guarantee that transactions are not nested.
|
|
||||||
#[inline]
|
|
||||||
pub fn new_unchecked(
|
|
||||||
conn: &Connection,
|
|
||||||
behavior: TransactionBehavior,
|
|
||||||
) -> Result<Transaction<'_>> {
|
|
||||||
let query = match behavior {
|
|
||||||
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
|
||||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
|
||||||
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
|
||||||
};
|
|
||||||
conn.execute_batch(query).map(move |()| Transaction {
|
|
||||||
conn,
|
|
||||||
drop_behavior: DropBehavior::Rollback,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
|
|
||||||
/// transactions.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// Just like outer level transactions, savepoint transactions rollback by
|
|
||||||
/// default.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
|
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
|
||||||
/// let mut tx = conn.transaction()?;
|
|
||||||
///
|
|
||||||
/// {
|
|
||||||
/// let sp = tx.savepoint()?;
|
|
||||||
/// if perform_queries_part_1_succeeds(&sp) {
|
|
||||||
/// sp.commit()?;
|
|
||||||
/// }
|
|
||||||
/// // otherwise, sp will rollback
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// tx.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::new_(self.conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
|
|
||||||
#[inline]
|
|
||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::with_name_(self.conn, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current setting for what happens to the transaction when it is
|
|
||||||
/// dropped.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn drop_behavior(&self) -> DropBehavior {
|
|
||||||
self.drop_behavior
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure the transaction to perform the specified action when it is
|
|
||||||
/// dropped.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
|
||||||
self.drop_behavior = drop_behavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A convenience method which consumes and commits a transaction.
|
|
||||||
#[inline]
|
|
||||||
pub fn commit(mut self) -> Result<()> {
|
|
||||||
self.commit_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn commit_(&mut self) -> Result<()> {
|
|
||||||
self.conn.execute_batch("COMMIT")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A convenience method which consumes and rolls back a transaction.
|
|
||||||
#[inline]
|
|
||||||
pub fn rollback(mut self) -> Result<()> {
|
|
||||||
self.rollback_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn rollback_(&mut self) -> Result<()> {
|
|
||||||
self.conn.execute_batch("ROLLBACK")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes the transaction, committing or rolling back according to the
|
|
||||||
/// current setting (see `drop_behavior`).
|
|
||||||
///
|
|
||||||
/// Functionally equivalent to the `Drop` implementation, but allows
|
|
||||||
/// callers to see any errors that occur.
|
|
||||||
#[inline]
|
|
||||||
pub fn finish(mut self) -> Result<()> {
|
|
||||||
self.finish_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn finish_(&mut self) -> Result<()> {
|
|
||||||
if self.conn.is_autocommit() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
match self.drop_behavior() {
|
|
||||||
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
|
|
||||||
DropBehavior::Rollback => self.rollback_(),
|
|
||||||
DropBehavior::Ignore => Ok(()),
|
|
||||||
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Transaction<'_> {
|
|
||||||
type Target = Connection;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Connection {
|
|
||||||
self.conn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(unused_must_use)]
|
|
||||||
impl Drop for Transaction<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.finish_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Savepoint<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
|
|
||||||
let name = name.into();
|
|
||||||
conn.execute_batch(&format!("SAVEPOINT {name}"))
|
|
||||||
.map(|()| Savepoint {
|
|
||||||
conn,
|
|
||||||
name,
|
|
||||||
drop_behavior: DropBehavior::Rollback,
|
|
||||||
committed: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_(conn: &Connection) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::with_name_(conn, "_rusqlite_sp")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new savepoint. Can be nested.
|
|
||||||
#[inline]
|
|
||||||
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::new_(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new savepoint with a user-provided savepoint name.
|
|
||||||
#[inline]
|
|
||||||
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::with_name_(conn, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a nested savepoint.
|
|
||||||
#[inline]
|
|
||||||
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::new_(self.conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a nested savepoint with a user-provided savepoint name.
|
|
||||||
#[inline]
|
|
||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::with_name_(self.conn, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current setting for what happens to the savepoint when it is
|
|
||||||
/// dropped.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn drop_behavior(&self) -> DropBehavior {
|
|
||||||
self.drop_behavior
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure the savepoint to perform the specified action when it is
|
|
||||||
/// dropped.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
|
||||||
self.drop_behavior = drop_behavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A convenience method which consumes and commits a savepoint.
|
|
||||||
#[inline]
|
|
||||||
pub fn commit(mut self) -> Result<()> {
|
|
||||||
self.commit_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn commit_(&mut self) -> Result<()> {
|
|
||||||
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
|
|
||||||
self.committed = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A convenience method which rolls back a savepoint.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// Unlike `Transaction`s, savepoints remain active after they have been
|
|
||||||
/// rolled back, and can be rolled back again or committed.
|
|
||||||
#[inline]
|
|
||||||
pub fn rollback(&mut self) -> Result<()> {
|
|
||||||
self.conn
|
|
||||||
.execute_batch(&format!("ROLLBACK TO {}", self.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes the savepoint, committing or rolling back according to the
|
|
||||||
/// current setting (see `drop_behavior`).
|
|
||||||
///
|
|
||||||
/// Functionally equivalent to the `Drop` implementation, but allows
|
|
||||||
/// callers to see any errors that occur.
|
|
||||||
#[inline]
|
|
||||||
pub fn finish(mut self) -> Result<()> {
|
|
||||||
self.finish_()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn finish_(&mut self) -> Result<()> {
|
|
||||||
if self.committed {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
match self.drop_behavior() {
|
|
||||||
DropBehavior::Commit => self
|
|
||||||
.commit_()
|
|
||||||
.or_else(|_| self.rollback().and_then(|()| self.commit_())),
|
|
||||||
DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()),
|
|
||||||
DropBehavior::Ignore => Ok(()),
|
|
||||||
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Savepoint<'_> {
|
|
||||||
type Target = Connection;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Connection {
|
|
||||||
self.conn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(unused_must_use)]
|
|
||||||
impl Drop for Savepoint<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.finish_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transaction state of a database
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
|
||||||
pub enum TransactionState {
|
|
||||||
/// Equivalent to `SQLITE_TXN_NONE`
|
|
||||||
None,
|
|
||||||
/// Equivalent to `SQLITE_TXN_READ`
|
|
||||||
Read,
|
|
||||||
/// Equivalent to `SQLITE_TXN_WRITE`
|
|
||||||
Write,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
/// Begin a new transaction with the default behavior (DEFERRED).
|
|
||||||
///
|
|
||||||
/// The transaction defaults to rolling back when it is dropped. If you
|
|
||||||
/// want the transaction to commit, you must call
|
|
||||||
/// [`commit`](Transaction::commit) or
|
|
||||||
/// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
|
||||||
/// let tx = conn.transaction()?;
|
|
||||||
///
|
|
||||||
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
||||||
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
|
||||||
///
|
|
||||||
/// tx.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn transaction(&mut self) -> Result<Transaction<'_>> {
|
|
||||||
Transaction::new(self, self.transaction_behavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new transaction with a specified behavior.
|
|
||||||
///
|
|
||||||
/// See [`transaction`](Connection::transaction).
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn transaction_with_behavior(
|
|
||||||
&mut self,
|
|
||||||
behavior: TransactionBehavior,
|
|
||||||
) -> Result<Transaction<'_>> {
|
|
||||||
Transaction::new(self, behavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new transaction with the default behavior (DEFERRED).
|
|
||||||
///
|
|
||||||
/// Attempt to open a nested transaction will result in a SQLite error.
|
|
||||||
/// `Connection::transaction` prevents this at compile time by taking `&mut
|
|
||||||
/// self`, but `Connection::unchecked_transaction()` may be used to defer
|
|
||||||
/// the checking until runtime.
|
|
||||||
///
|
|
||||||
/// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
|
|
||||||
/// (which can be used if the default transaction behavior is undesirable).
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # use std::rc::Rc;
|
|
||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
|
|
||||||
/// let tx = conn.unchecked_transaction()?;
|
|
||||||
///
|
|
||||||
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
||||||
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
|
||||||
///
|
|
||||||
/// tx.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite call fails. The specific
|
|
||||||
/// error returned if transactions are nested is currently unspecified.
|
|
||||||
pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
|
|
||||||
Transaction::new_unchecked(self, self.transaction_behavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new savepoint with the default behavior (DEFERRED).
|
|
||||||
///
|
|
||||||
/// The savepoint defaults to rolling back when it is dropped. If you want
|
|
||||||
/// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
|
|
||||||
/// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
|
||||||
/// let sp = conn.savepoint()?;
|
|
||||||
///
|
|
||||||
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
|
|
||||||
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
|
|
||||||
///
|
|
||||||
/// sp.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Begin a new savepoint with a specified name.
|
|
||||||
///
|
|
||||||
/// See [`savepoint`](Connection::savepoint).
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
|
||||||
#[inline]
|
|
||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
|
||||||
Savepoint::with_name(self, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the transaction state of a database
|
|
||||||
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
|
||||||
pub fn transaction_state<N: crate::Name>(
|
|
||||||
&self,
|
|
||||||
db_name: Option<N>,
|
|
||||||
) -> Result<TransactionState> {
|
|
||||||
self.db.borrow().txn_state(db_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the default transaction behavior for the connection.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// This will only apply to transactions initiated by [`transaction`](Connection::transaction)
|
|
||||||
/// or [`unchecked_transaction`](Connection::unchecked_transaction).
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result, TransactionBehavior};
|
|
||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
|
||||||
/// conn.set_transaction_behavior(TransactionBehavior::Immediate);
|
|
||||||
///
|
|
||||||
/// let tx = conn.transaction()?;
|
|
||||||
///
|
|
||||||
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
||||||
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
|
||||||
///
|
|
||||||
/// tx.commit()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {
|
|
||||||
self.transaction_behavior = behavior;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::DropBehavior;
|
|
||||||
use crate::{Connection, Error, Result};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_drop() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
{
|
|
||||||
let tx = db.transaction()?;
|
|
||||||
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
||||||
// default: rollback
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut tx = db.transaction()?;
|
|
||||||
tx.execute_batch("INSERT INTO foo VALUES(2)")?;
|
|
||||||
tx.set_drop_behavior(DropBehavior::Commit)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let tx = db.transaction()?;
|
|
||||||
assert_eq!(2, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn assert_nested_tx_error(e: Error) {
|
|
||||||
if let Error::SqliteFailure(e, Some(m)) = &e {
|
|
||||||
assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
|
|
||||||
// FIXME: Not ideal...
|
|
||||||
assert_eq!(e.code, crate::ErrorCode::Unknown);
|
|
||||||
assert!(m.contains("transaction"));
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected error type: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unchecked_nesting() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let tx = db.unchecked_transaction()?;
|
|
||||||
let e = tx.unchecked_transaction().unwrap_err();
|
|
||||||
assert_nested_tx_error(e);
|
|
||||||
// default: rollback
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let tx = db.unchecked_transaction()?;
|
|
||||||
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
||||||
// Ensure this doesn't interfere with ongoing transaction
|
|
||||||
let e = tx.unchecked_transaction().unwrap_err();
|
|
||||||
assert_nested_tx_error(e);
|
|
||||||
|
|
||||||
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
||||||
tx.commit()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(2, db.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_explicit_rollback_commit() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
{
|
|
||||||
let mut tx = db.transaction()?;
|
|
||||||
{
|
|
||||||
let mut sp = tx.savepoint()?;
|
|
||||||
sp.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
||||||
sp.rollback()?;
|
|
||||||
sp.execute_batch("INSERT INTO foo VALUES(2)")?;
|
|
||||||
sp.commit()?;
|
|
||||||
}
|
|
||||||
tx.commit()?;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let tx = db.transaction()?;
|
|
||||||
tx.execute_batch("INSERT INTO foo VALUES(4)")?;
|
|
||||||
tx.commit()?;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let tx = db.transaction()?;
|
|
||||||
assert_eq!(6, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_savepoint() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
{
|
|
||||||
let mut tx = db.transaction()?;
|
|
||||||
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
||||||
assert_current_sum(1, &tx)?;
|
|
||||||
tx.set_drop_behavior(DropBehavior::Commit);
|
|
||||||
{
|
|
||||||
let mut sp1 = tx.savepoint()?;
|
|
||||||
sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
|
|
||||||
assert_current_sum(3, &sp1)?;
|
|
||||||
// will roll back sp1
|
|
||||||
{
|
|
||||||
let mut sp2 = sp1.savepoint()?;
|
|
||||||
sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
|
|
||||||
assert_current_sum(7, &sp2)?;
|
|
||||||
// will roll back sp2
|
|
||||||
{
|
|
||||||
let sp3 = sp2.savepoint()?;
|
|
||||||
sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
|
|
||||||
assert_current_sum(15, &sp3)?;
|
|
||||||
sp3.commit()?;
|
|
||||||
// committed sp3, but will be erased by sp2 rollback
|
|
||||||
}
|
|
||||||
assert_current_sum(15, &sp2)?;
|
|
||||||
}
|
|
||||||
assert_current_sum(3, &sp1)?;
|
|
||||||
}
|
|
||||||
assert_current_sum(1, &tx)?;
|
|
||||||
}
|
|
||||||
assert_current_sum(1, &db)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ignore_drop_behavior() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let mut tx = db.transaction()?;
|
|
||||||
{
|
|
||||||
let mut sp1 = tx.savepoint()?;
|
|
||||||
insert(1, &sp1)?;
|
|
||||||
sp1.rollback()?;
|
|
||||||
insert(2, &sp1)?;
|
|
||||||
{
|
|
||||||
let mut sp2 = sp1.savepoint()?;
|
|
||||||
sp2.set_drop_behavior(DropBehavior::Ignore);
|
|
||||||
insert(4, &sp2)?;
|
|
||||||
}
|
|
||||||
assert_current_sum(6, &sp1)?;
|
|
||||||
sp1.commit()?;
|
|
||||||
}
|
|
||||||
assert_current_sum(6, &tx)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_savepoint_drop_behavior_releases() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut sp = db.savepoint()?;
|
|
||||||
sp.set_drop_behavior(DropBehavior::Commit);
|
|
||||||
}
|
|
||||||
assert!(db.is_autocommit());
|
|
||||||
{
|
|
||||||
let mut sp = db.savepoint()?;
|
|
||||||
sp.set_drop_behavior(DropBehavior::Rollback);
|
|
||||||
}
|
|
||||||
assert!(db.is_autocommit());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_savepoint_release_error() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
db.pragma_update(None, "foreign_keys", true)?;
|
|
||||||
db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
|
|
||||||
{
|
|
||||||
let mut sp = db.savepoint()?;
|
|
||||||
sp.execute("INSERT INTO f VALUES (0)", [])?;
|
|
||||||
sp.set_drop_behavior(DropBehavior::Commit);
|
|
||||||
}
|
|
||||||
assert!(db.is_autocommit());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_savepoint_names() -> Result<()> {
|
|
||||||
let mut db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut sp1 = db.savepoint_with_name("my_sp")?;
|
|
||||||
insert(1, &sp1)?;
|
|
||||||
assert_current_sum(1, &sp1)?;
|
|
||||||
{
|
|
||||||
let mut sp2 = sp1.savepoint_with_name("my_sp")?;
|
|
||||||
sp2.set_drop_behavior(DropBehavior::Commit);
|
|
||||||
insert(2, &sp2)?;
|
|
||||||
assert_current_sum(3, &sp2)?;
|
|
||||||
sp2.rollback()?;
|
|
||||||
assert_current_sum(1, &sp2)?;
|
|
||||||
insert(4, &sp2)?;
|
|
||||||
}
|
|
||||||
assert_current_sum(5, &sp1)?;
|
|
||||||
sp1.rollback()?;
|
|
||||||
{
|
|
||||||
let mut sp2 = sp1.savepoint_with_name("my_sp")?;
|
|
||||||
sp2.set_drop_behavior(DropBehavior::Ignore);
|
|
||||||
insert(8, &sp2)?;
|
|
||||||
}
|
|
||||||
assert_current_sum(8, &sp1)?;
|
|
||||||
sp1.commit()?;
|
|
||||||
}
|
|
||||||
assert_current_sum(8, &db)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rc() -> Result<()> {
|
|
||||||
use std::rc::Rc;
|
|
||||||
let mut conn = Connection::open_in_memory()?;
|
|
||||||
let rc_txn = Rc::new(conn.transaction()?);
|
|
||||||
|
|
||||||
// This will compile only if Transaction is Debug
|
|
||||||
Rc::try_unwrap(rc_txn).unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(x: i32, conn: &Connection) -> Result<usize> {
|
|
||||||
conn.execute("INSERT INTO foo VALUES(?1)", [x])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
|
|
||||||
assert_eq!(x, conn.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
fn txn_state() -> Result<()> {
|
|
||||||
use super::TransactionState;
|
|
||||||
use crate::MAIN_DB;
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
assert_eq!(TransactionState::None, db.transaction_state(Some(MAIN_DB))?);
|
|
||||||
assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
|
|
||||||
db.execute_batch("BEGIN")?;
|
|
||||||
assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
|
|
||||||
let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
|
||||||
assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
|
|
||||||
db.pragma_update(None, "user_version", 1)?;
|
|
||||||
assert_eq!(TransactionState::Write, db.transaction_state::<&str>(None)?);
|
|
||||||
db.execute_batch("ROLLBACK")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "modern_sqlite")]
|
|
||||||
fn auto_commit() -> Result<()> {
|
|
||||||
use super::TransactionState;
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE t(i UNIQUE);")?;
|
|
||||||
assert!(db.is_autocommit());
|
|
||||||
let mut stmt = db.prepare("SELECT name FROM sqlite_master")?;
|
|
||||||
assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
|
|
||||||
{
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
assert!(rows.next()?.is_some()); // start reading
|
|
||||||
assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
|
|
||||||
db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit
|
|
||||||
assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
|
|
||||||
assert!(rows.next()?.is_some()); // still reading
|
|
||||||
assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
|
|
||||||
assert!(rows.next()?.is_none()); // end
|
|
||||||
assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
317
vendor/rusqlite/src/types/chrono.rs
vendored
317
vendor/rusqlite/src/types/chrono.rs
vendored
@@ -1,317 +0,0 @@
|
|||||||
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef};
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
|
||||||
impl ToSql for NaiveDate {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.format("%F").to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
|
||||||
impl FromSql for NaiveDate {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value
|
|
||||||
.as_str()
|
|
||||||
.and_then(|s| Self::parse_from_str(s, "%F").map_err(FromSqlError::other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
|
||||||
impl ToSql for NaiveTime {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.format("%T%.f").to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
|
|
||||||
impl FromSql for NaiveTime {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().and_then(|s| {
|
|
||||||
let fmt = match s.len() {
|
|
||||||
5 => "%H:%M",
|
|
||||||
8 => "%T",
|
|
||||||
_ => "%T%.f",
|
|
||||||
};
|
|
||||||
Self::parse_from_str(s, fmt).map_err(FromSqlError::other)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ISO 8601 combined date and time without timezone =>
|
|
||||||
/// "YYYY-MM-DD HH:MM:SS.SSS"
|
|
||||||
impl ToSql for NaiveDateTime {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.format("%F %T%.f").to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date
|
|
||||||
/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
|
|
||||||
/// also supported)
|
|
||||||
impl FromSql for NaiveDateTime {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().and_then(|s| {
|
|
||||||
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
|
||||||
"%FT%T%.f"
|
|
||||||
} else {
|
|
||||||
"%F %T%.f"
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::parse_from_str(s, fmt).map_err(FromSqlError::other)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UTC time => UTC RFC3339 timestamp
|
|
||||||
/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
|
|
||||||
impl ToSql for DateTime<Utc> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.format("%F %T%.f%:z").to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Local time => UTC RFC3339 timestamp
|
|
||||||
/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
|
|
||||||
impl ToSql for DateTime<Local> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Date and time with time zone => RFC3339 timestamp
|
|
||||||
/// ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM").
|
|
||||||
impl ToSql for DateTime<FixedOffset> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.format("%F %T%.f%:z").to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") or unix timestamp (in seconds) into `DateTime<Utc>`.
|
|
||||||
impl FromSql for DateTime<Utc> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
if value.data_type() == Type::Integer {
|
|
||||||
return value.as_i64().and_then(|i| {
|
|
||||||
DateTime::from_timestamp_secs(i).ok_or_else(|| FromSqlError::OutOfRange(i))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Try to parse value as rfc3339 first.
|
|
||||||
let s = value.as_str()?;
|
|
||||||
|
|
||||||
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
|
||||||
"%FT%T%.f%#z"
|
|
||||||
} else {
|
|
||||||
"%F %T%.f%#z"
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
|
|
||||||
return Ok(dt.with_timezone(&Utc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Couldn't parse as rfc3339 - fall back to NaiveDateTime.
|
|
||||||
NaiveDateTime::column_result(value).map(|dt| Utc.from_utc_datetime(&dt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") or unix timestamp (in seconds) into `DateTime<Local>`.
|
|
||||||
impl FromSql for DateTime<Local> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let utc_dt = DateTime::<Utc>::column_result(value)?;
|
|
||||||
Ok(utc_dt.with_timezone(&Local))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<FixedOffset>`.
|
|
||||||
impl FromSql for DateTime<FixedOffset> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let s = String::column_result(value)?;
|
|
||||||
Self::parse_from_rfc3339(s.as_str())
|
|
||||||
.or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z"))
|
|
||||||
.map_err(FromSqlError::other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
types::{FromSql, ValueRef},
|
|
||||||
Connection, Result,
|
|
||||||
};
|
|
||||||
use chrono::{
|
|
||||||
DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone,
|
|
||||||
Timelike, Utc,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER AS (strftime('%s', t)), b BLOB)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_naive_date() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23", s);
|
|
||||||
let t: NaiveDate = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(date, t);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_naive_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("23:56:04", s);
|
|
||||||
let v: NaiveTime = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(time, v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_naive_date_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
|
||||||
let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
|
|
||||||
let dt = NaiveDateTime::new(date, time);
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23 23:56:04", s);
|
|
||||||
let v: NaiveDateTime = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(dt, v);
|
|
||||||
|
|
||||||
db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
|
|
||||||
let hms: NaiveDateTime = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(dt, hms);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_time_utc() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
|
||||||
let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
|
|
||||||
let dt = NaiveDateTime::new(date, time);
|
|
||||||
let utc = Utc.from_utc_datetime(&dt);
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [utc])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23 23:56:04.789+00:00", s);
|
|
||||||
|
|
||||||
let v1: DateTime<Utc> = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(utc, v1);
|
|
||||||
let v1: DateTime<Utc> = db.one_column("SELECT i FROM foo", [])?;
|
|
||||||
assert_eq!(utc.with_nanosecond(0).unwrap(), v1);
|
|
||||||
|
|
||||||
let v2: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789'", [])?;
|
|
||||||
assert_eq!(utc, v2);
|
|
||||||
|
|
||||||
let v3: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04'", [])?;
|
|
||||||
assert_eq!(utc - Duration::try_milliseconds(789).unwrap(), v3);
|
|
||||||
|
|
||||||
let v4: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789+00:00'", [])?;
|
|
||||||
assert_eq!(utc, v4);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_time_local() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
|
|
||||||
let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
|
|
||||||
let dt = NaiveDateTime::new(date, time);
|
|
||||||
let local = Local.from_local_datetime(&dt).single().unwrap();
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [local])?;
|
|
||||||
|
|
||||||
// Stored string should be in UTC
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert!(s.ends_with("+00:00"));
|
|
||||||
|
|
||||||
let v: DateTime<Local> = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(local, v);
|
|
||||||
let v: DateTime<Local> = db.one_column("SELECT i FROM foo", [])?;
|
|
||||||
assert_eq!(local.with_nanosecond(0).unwrap(), v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_time_fixed() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap();
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
|
||||||
|
|
||||||
// Stored string should preserve timezone offset
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert!(s.ends_with("+04:00"));
|
|
||||||
|
|
||||||
let v: DateTime<FixedOffset> = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(time.offset(), v.offset());
|
|
||||||
assert_eq!(time, v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sqlite_functions() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
db.one_column::<NaiveTime, _>("SELECT CURRENT_TIME", [])?;
|
|
||||||
db.one_column::<NaiveDate, _>("SELECT CURRENT_DATE", [])?;
|
|
||||||
db.one_column::<NaiveDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
|
||||||
db.one_column::<DateTime<Utc>, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_naive_date_time_param() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
db.one_column::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_time_param() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
db.one_column::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lenient_parse_timezone() {
|
|
||||||
DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).unwrap();
|
|
||||||
DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
507
vendor/rusqlite/src/types/from_sql.rs
vendored
507
vendor/rusqlite/src/types/from_sql.rs
vendored
@@ -1,507 +0,0 @@
|
|||||||
use super::{Value, ValueRef};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::str::Utf8Error;
|
|
||||||
|
|
||||||
/// Enum listing possible errors from [`FromSql`] trait.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum FromSqlError {
|
|
||||||
/// Error when an SQLite value is requested, but the type of the result
|
|
||||||
/// cannot be converted to the requested Rust type.
|
|
||||||
InvalidType,
|
|
||||||
|
|
||||||
/// Error when the i64 value returned by SQLite cannot be stored into the
|
|
||||||
/// requested type.
|
|
||||||
OutOfRange(i64),
|
|
||||||
|
|
||||||
/// Error converting a string to UTF-8.
|
|
||||||
Utf8Error(Utf8Error),
|
|
||||||
|
|
||||||
/// Error when the blob result returned by SQLite cannot be stored into the
|
|
||||||
/// requested type due to a size mismatch.
|
|
||||||
InvalidBlobSize {
|
|
||||||
/// The expected size of the blob.
|
|
||||||
expected_size: usize,
|
|
||||||
/// The actual size of the blob that was returned.
|
|
||||||
blob_size: usize,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An error case available for implementors of the [`FromSql`] trait.
|
|
||||||
Other(Box<dyn Error + Send + Sync + 'static>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSqlError {
|
|
||||||
/// Converts an arbitrary error type to [`FromSqlError`].
|
|
||||||
///
|
|
||||||
/// This is a convenience function that boxes and unsizes the error type. It's main purpose is
|
|
||||||
/// to be usable in the `map_err` method. So instead of
|
|
||||||
/// `result.map_err(|error| FromSqlError::Other(Box::new(error))` you can write
|
|
||||||
/// `result.map_err(FromSqlError::other)`.
|
|
||||||
pub fn other<E: Error + Send + Sync + 'static>(error: E) -> Self {
|
|
||||||
Self::Other(Box::new(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for FromSqlError {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::InvalidType, Self::InvalidType) => true,
|
|
||||||
(Self::OutOfRange(n1), Self::OutOfRange(n2)) => n1 == n2,
|
|
||||||
(Self::Utf8Error(u1), Self::Utf8Error(u2)) => u1 == u2,
|
|
||||||
(
|
|
||||||
Self::InvalidBlobSize {
|
|
||||||
expected_size: es1,
|
|
||||||
blob_size: bs1,
|
|
||||||
},
|
|
||||||
Self::InvalidBlobSize {
|
|
||||||
expected_size: es2,
|
|
||||||
blob_size: bs2,
|
|
||||||
},
|
|
||||||
) => es1 == es2 && bs1 == bs2,
|
|
||||||
(..) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FromSqlError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Self::InvalidType => write!(f, "Invalid type"),
|
|
||||||
Self::OutOfRange(i) => write!(f, "Value {i} out of range"),
|
|
||||||
Self::Utf8Error(ref err) => err.fmt(f),
|
|
||||||
Self::InvalidBlobSize {
|
|
||||||
expected_size,
|
|
||||||
blob_size,
|
|
||||||
} => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Cannot read {expected_size} byte value out of {blob_size} byte blob"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::Other(ref err) => err.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for FromSqlError {
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Self::Utf8Error(ref err) => Some(err),
|
|
||||||
Self::Other(ref err) => Some(&**err),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result type for implementors of the [`FromSql`] trait.
|
|
||||||
pub type FromSqlResult<T> = Result<T, FromSqlError>;
|
|
||||||
|
|
||||||
/// A trait for types that can be created from a SQLite value.
|
|
||||||
pub trait FromSql: Sized {
|
|
||||||
/// Converts SQLite value into Rust value.
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! from_sql_integral(
|
|
||||||
($t:ident) => (
|
|
||||||
impl FromSql for $t {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let i = i64::column_result(value)?;
|
|
||||||
i.try_into().map_err(|_| FromSqlError::OutOfRange(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
(non_zero $nz:ty, $z:ty) => (
|
|
||||||
impl FromSql for $nz {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let i = <$z>::column_result(value)?;
|
|
||||||
<$nz>::new(i).ok_or(FromSqlError::OutOfRange(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
from_sql_integral!(i8);
|
|
||||||
from_sql_integral!(i16);
|
|
||||||
from_sql_integral!(i32);
|
|
||||||
// from_sql_integral!(i64); // Not needed because the native type is i64.
|
|
||||||
from_sql_integral!(isize);
|
|
||||||
from_sql_integral!(u8);
|
|
||||||
from_sql_integral!(u16);
|
|
||||||
from_sql_integral!(u32);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
from_sql_integral!(u64);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
from_sql_integral!(usize);
|
|
||||||
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroIsize, isize);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroI8, i8);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroI16, i16);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroI32, i32);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroI64, i64);
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroI128, i128);
|
|
||||||
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroUsize, usize);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroU8, u8);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroU16, u16);
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroU32, u32);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
from_sql_integral!(non_zero std::num::NonZeroU64, u64);
|
|
||||||
// std::num::NonZeroU128 is not supported since u128 isn't either
|
|
||||||
|
|
||||||
impl FromSql for i64 {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_i64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for f32 {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
match value {
|
|
||||||
ValueRef::Integer(i) => Ok(i as Self),
|
|
||||||
ValueRef::Real(f) => Ok(f as Self),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for f64 {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
match value {
|
|
||||||
ValueRef::Integer(i) => Ok(i as Self),
|
|
||||||
ValueRef::Real(f) => Ok(f),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for bool {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
i64::column_result(value).map(|i| i != 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for String {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().map(ToString::to_string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Box<str> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().map(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for std::rc::Rc<str> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().map(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for std::sync::Arc<str> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().map(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Vec<u8> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_blob().map(<[u8]>::to_vec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Box<[u8]> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_blob().map(Box::<[u8]>::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for std::rc::Rc<[u8]> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_blob().map(std::rc::Rc::<[u8]>::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for std::sync::Arc<[u8]> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_blob().map(std::sync::Arc::<[u8]>::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> FromSql for [u8; N] {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let slice = value.as_blob()?;
|
|
||||||
slice.try_into().map_err(|_| FromSqlError::InvalidBlobSize {
|
|
||||||
expected_size: N,
|
|
||||||
blob_size: slice.len(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
impl FromSql for i128 {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let bytes = <[u8; 16]>::column_result(value)?;
|
|
||||||
Ok(Self::from_be_bytes(bytes) ^ (1_i128 << 127))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
impl FromSql for uuid::Uuid {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
let bytes = <[u8; 16]>::column_result(value)?;
|
|
||||||
Ok(Self::from_u128(u128::from_be_bytes(bytes)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: FromSql> FromSql for Option<T> {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
match value {
|
|
||||||
ValueRef::Null => Ok(None),
|
|
||||||
_ => FromSql::column_result(value).map(Some),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> FromSql for Cow<'_, T>
|
|
||||||
where
|
|
||||||
T: ToOwned,
|
|
||||||
T::Owned: FromSql,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
<T::Owned>::column_result(value).map(Cow::Owned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Value {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.try_into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::{FromSql, FromSqlError};
|
|
||||||
use crate::{Connection, Error, Result};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_integral_ranges() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
|
|
||||||
where
|
|
||||||
T: Into<i64> + FromSql + std::fmt::Debug,
|
|
||||||
{
|
|
||||||
for n in out_of_range {
|
|
||||||
let err = db
|
|
||||||
.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
|
|
||||||
.unwrap_err();
|
|
||||||
match err {
|
|
||||||
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
|
|
||||||
_ => panic!("unexpected error: {err}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for n in in_range {
|
|
||||||
assert_eq!(
|
|
||||||
*n,
|
|
||||||
db.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
|
|
||||||
.unwrap()
|
|
||||||
.into()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
|
|
||||||
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
|
|
||||||
check_ranges::<i32>(
|
|
||||||
&db,
|
|
||||||
&[-2_147_483_649, 2_147_483_648],
|
|
||||||
&[-2_147_483_648, -1, 0, 1, 2_147_483_647],
|
|
||||||
);
|
|
||||||
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
|
|
||||||
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
|
|
||||||
check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_nonzero_ranges() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
macro_rules! check_ranges {
|
|
||||||
($nz:ty, $out_of_range:expr, $in_range:expr) => {
|
|
||||||
for &n in $out_of_range {
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT ?1", [n], |r| r.get::<_, $nz>(0)),
|
|
||||||
Err(Error::IntegralValueOutOfRange(0, n)),
|
|
||||||
"{}",
|
|
||||||
std::any::type_name::<$nz>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for &n in $in_range {
|
|
||||||
let non_zero = <$nz>::new(n).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
Ok(non_zero),
|
|
||||||
db.query_row("SELECT ?1", [non_zero], |r| r.get::<_, $nz>(0))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
check_ranges!(std::num::NonZeroI8, &[0, -129, 128], &[-128, 1, 127]);
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroI16,
|
|
||||||
&[0, -32769, 32768],
|
|
||||||
&[-32768, -1, 1, 32767]
|
|
||||||
);
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroI32,
|
|
||||||
&[0, -2_147_483_649, 2_147_483_648],
|
|
||||||
&[-2_147_483_648, -1, 1, 2_147_483_647]
|
|
||||||
);
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroI64,
|
|
||||||
&[0],
|
|
||||||
&[-2_147_483_648, -1, 1, 2_147_483_647, i64::MAX, i64::MIN]
|
|
||||||
);
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroIsize,
|
|
||||||
&[0],
|
|
||||||
&[-2_147_483_648, -1, 1, 2_147_483_647]
|
|
||||||
);
|
|
||||||
check_ranges!(std::num::NonZeroU8, &[0, -2, -1, 256], &[1, 255]);
|
|
||||||
check_ranges!(std::num::NonZeroU16, &[0, -2, -1, 65536], &[1, 65535]);
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroU32,
|
|
||||||
&[0, -2, -1, 4_294_967_296],
|
|
||||||
&[1, 4_294_967_295]
|
|
||||||
);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroU64,
|
|
||||||
&[0, -2, -1, -4_294_967_296],
|
|
||||||
&[1, 4_294_967_295, i64::MAX as u64]
|
|
||||||
);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
check_ranges!(
|
|
||||||
std::num::NonZeroUsize,
|
|
||||||
&[0, -2, -1, -4_294_967_296],
|
|
||||||
&[1, 4_294_967_295]
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cow() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT 'this is a string'", [], |r| r
|
|
||||||
.get::<_, Cow<'_, str>>(0)),
|
|
||||||
Ok(Cow::Borrowed("this is a string")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT x'09ab20fdee87'", [], |r| r
|
|
||||||
.get::<_, Cow<'_, [u8]>>(0)),
|
|
||||||
Ok(Cow::Owned(vec![0x09, 0xab, 0x20, 0xfd, 0xee, 0x87])),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT 24.5", [], |r| r.get::<_, Cow<'_, f32>>(0),),
|
|
||||||
Ok(Cow::Borrowed(&24.5)),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_heap_slice() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT 'text'", [], |r| r.get::<_, Box<str>>(0)),
|
|
||||||
Ok(Box::from("text")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT 'Some string slice!'", [], |r| r
|
|
||||||
.get::<_, Rc<str>>(0)),
|
|
||||||
Ok(Rc::from("Some string slice!")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT x'012366779988fedc'", [], |r| r
|
|
||||||
.get::<_, Rc<[u8]>>(0)),
|
|
||||||
Ok(Rc::from(b"\x01\x23\x66\x77\x99\x88\xfe\xdc".as_slice())),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row(
|
|
||||||
"SELECT x'6120737472696e672043414e206265206120626c6f62'",
|
|
||||||
[],
|
|
||||||
|r| r.get::<_, Box<[u8]>>(0)
|
|
||||||
),
|
|
||||||
Ok(b"a string CAN be a blob".to_vec().into_boxed_slice()),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT 'This is inside an Arc.'", [], |r| r
|
|
||||||
.get::<_, Arc<str>>(0)),
|
|
||||||
Ok(Arc::from("This is inside an Arc.")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.query_row("SELECT x'afd374'", [], |r| r.get::<_, Arc<[u8]>>(0),),
|
|
||||||
Ok(Arc::from(b"\xaf\xd3\x74".as_slice())),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn from_sql_error() {
|
|
||||||
use std::error::Error as _;
|
|
||||||
assert_ne!(FromSqlError::InvalidType, FromSqlError::OutOfRange(0));
|
|
||||||
assert_ne!(FromSqlError::OutOfRange(0), FromSqlError::OutOfRange(1));
|
|
||||||
assert_ne!(
|
|
||||||
FromSqlError::InvalidBlobSize {
|
|
||||||
expected_size: 0,
|
|
||||||
blob_size: 0
|
|
||||||
},
|
|
||||||
FromSqlError::InvalidBlobSize {
|
|
||||||
expected_size: 0,
|
|
||||||
blob_size: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert!(FromSqlError::InvalidType.source().is_none());
|
|
||||||
let err = std::io::Error::from(std::io::ErrorKind::UnexpectedEof);
|
|
||||||
assert!(FromSqlError::Other(Box::new(err)).source().is_some());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
223
vendor/rusqlite/src/types/jiff.rs
vendored
223
vendor/rusqlite/src/types/jiff.rs
vendored
@@ -1,223 +0,0 @@
|
|||||||
//! Convert some `jiff` types.
|
|
||||||
|
|
||||||
use jiff::{
|
|
||||||
civil::{Date, DateTime, Time},
|
|
||||||
Timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef};
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
/// Gregorian calendar date => "YYYY-MM-DD"
|
|
||||||
impl ToSql for Date {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let s = self.to_string();
|
|
||||||
Ok(ToSqlOutput::from(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "YYYY-MM-DD" => Gregorian calendar date.
|
|
||||||
impl FromSql for Date {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value
|
|
||||||
.as_str()
|
|
||||||
.and_then(|s| s.parse().map_err(FromSqlError::other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// time => "HH:MM:SS.SSS"
|
|
||||||
impl ToSql for Time {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self.to_string();
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "HH:MM:SS.SSS" => time.
|
|
||||||
impl FromSql for Time {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value
|
|
||||||
.as_str()
|
|
||||||
.and_then(|s| s.parse().map_err(FromSqlError::other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gregorian datetime => "YYYY-MM-DDTHH:MM:SS.SSS"
|
|
||||||
impl ToSql for DateTime {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let s = self.to_string();
|
|
||||||
Ok(ToSqlOutput::from(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "YYYY-MM-DDTHH:MM:SS.SSS" => Gregorian datetime.
|
|
||||||
impl FromSql for DateTime {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value
|
|
||||||
.as_str()
|
|
||||||
.and_then(|s| s.parse().map_err(FromSqlError::other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UTC time => UTC RFC3339 timestamp
|
|
||||||
/// ("YYYY-MM-DDTHH:MM:SS.SSSZ").
|
|
||||||
impl ToSql for Timestamp {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") or unix timestamp (in seconds) into `Timestamp`.
|
|
||||||
impl FromSql for Timestamp {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
if value.data_type() == Type::Integer {
|
|
||||||
return value
|
|
||||||
.as_i64()
|
|
||||||
.and_then(|i| Timestamp::from_second(i).map_err(FromSqlError::other));
|
|
||||||
}
|
|
||||||
value
|
|
||||||
.as_str()?
|
|
||||||
.parse::<Timestamp>()
|
|
||||||
.map_err(FromSqlError::other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use jiff::{
|
|
||||||
civil::{Date, DateTime, Time},
|
|
||||||
Timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER AS (strftime('%s', t)), b BLOB)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let date = Date::constant(2016, 2, 23);
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23", s);
|
|
||||||
let t: Date = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(date, t);
|
|
||||||
|
|
||||||
db.execute("UPDATE foo set b = date(t)", [])?;
|
|
||||||
let t: Date = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(date, t);
|
|
||||||
|
|
||||||
let r: Result<Date> = db.one_column("SELECT '2023-02-29'", []);
|
|
||||||
assert!(r.is_err());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let time = Time::constant(23, 56, 4, 0);
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("23:56:04", s);
|
|
||||||
let v: Time = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(time, v);
|
|
||||||
|
|
||||||
db.execute("UPDATE foo set b = time(t)", [])?;
|
|
||||||
let v: Time = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(time, v);
|
|
||||||
|
|
||||||
let r: Result<Time> = db.one_column("SELECT '25:22:45'", []);
|
|
||||||
assert!(r.is_err());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let dt = DateTime::constant(2016, 2, 23, 23, 56, 4, 0);
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23T23:56:04", s);
|
|
||||||
let v: DateTime = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(dt, v);
|
|
||||||
|
|
||||||
db.execute("UPDATE foo set b = datetime(t)", [])?;
|
|
||||||
let v: DateTime = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(dt, v);
|
|
||||||
|
|
||||||
let r: Result<DateTime> = db.one_column("SELECT '2023-02-29T00:00:00'", []);
|
|
||||||
assert!(r.is_err());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_timestamp() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let ts: Timestamp = "2016-02-23 23:56:04Z".parse().unwrap();
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [ts])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23T23:56:04Z", s);
|
|
||||||
let v: Timestamp = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(ts, v);
|
|
||||||
let v: Timestamp = db.one_column("SELECT i FROM foo", [])?;
|
|
||||||
assert_eq!(ts, v);
|
|
||||||
|
|
||||||
let r: Result<Timestamp> = db.one_column("SELECT '2023-02-29T00:00:00Z'", []);
|
|
||||||
assert!(r.is_err());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_timestamp_various_formats() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
// Copied over from a test in `src/types/time.rs`. The format numbers
|
|
||||||
// come from <https://sqlite.org/lang_datefunc.html>.
|
|
||||||
let tests = vec![
|
|
||||||
// Rfc3339
|
|
||||||
"2013-10-07T08:23:19.123456789Z",
|
|
||||||
"2013-10-07 08:23:19.123456789Z",
|
|
||||||
// Format 2
|
|
||||||
"2013-10-07 08:23Z",
|
|
||||||
"2013-10-07 08:23+04:00",
|
|
||||||
// Format 3
|
|
||||||
"2013-10-07 08:23:19Z",
|
|
||||||
"2013-10-07 08:23:19+04:00",
|
|
||||||
// Format 4
|
|
||||||
"2013-10-07 08:23:19.123Z",
|
|
||||||
"2013-10-07 08:23:19.123+04:00",
|
|
||||||
// Format 5
|
|
||||||
"2013-10-07T08:23Z",
|
|
||||||
"2013-10-07T08:23+04:00",
|
|
||||||
// Format 6
|
|
||||||
"2013-10-07T08:23:19Z",
|
|
||||||
"2013-10-07T08:23:19+04:00",
|
|
||||||
// Format 7
|
|
||||||
"2013-10-07T08:23:19.123Z",
|
|
||||||
"2013-10-07T08:23:19.123+04:00",
|
|
||||||
];
|
|
||||||
|
|
||||||
for string in tests {
|
|
||||||
let expected: Timestamp = string.parse().unwrap();
|
|
||||||
let result: Timestamp = db.one_column("SELECT ?1", [string])?;
|
|
||||||
assert_eq!(result, expected);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
457
vendor/rusqlite/src/types/mod.rs
vendored
457
vendor/rusqlite/src/types/mod.rs
vendored
@@ -1,457 +0,0 @@
|
|||||||
//! Traits dealing with SQLite data types.
|
|
||||||
//!
|
|
||||||
//! SQLite uses a [dynamic type system](https://www.sqlite.org/datatype3.html). Implementations of
|
|
||||||
//! the [`ToSql`] and [`FromSql`] traits are provided for the basic types that
|
|
||||||
//! SQLite provides methods for:
|
|
||||||
//!
|
|
||||||
//! * Strings (`String` and `&str`)
|
|
||||||
//! * Blobs (`Vec<u8>` and `&[u8]`)
|
|
||||||
//! * Numbers
|
|
||||||
//!
|
|
||||||
//! The number situation is a little complicated due to the fact that all
|
|
||||||
//! numbers in SQLite are stored as `INTEGER` (`i64`) or `REAL` (`f64`).
|
|
||||||
//!
|
|
||||||
//! [`ToSql`] and [`FromSql`] are implemented for all primitive number types.
|
|
||||||
//! [`FromSql`] has different behaviour depending on the SQL and Rust types, and
|
|
||||||
//! the value.
|
|
||||||
//!
|
|
||||||
//! * `INTEGER` to integer: returns an
|
|
||||||
//! [`Error::IntegralValueOutOfRange`](crate::Error::IntegralValueOutOfRange)
|
|
||||||
//! error if the value does not fit in the Rust type.
|
|
||||||
//! * `REAL` to integer: always returns an
|
|
||||||
//! [`Error::InvalidColumnType`](crate::Error::InvalidColumnType) error.
|
|
||||||
//! * `INTEGER` to float: casts using `as` operator. Never fails.
|
|
||||||
//! * `REAL` to float: casts using `as` operator. Never fails.
|
|
||||||
//!
|
|
||||||
//! [`ToSql`] always succeeds except when storing a `u64` or `usize` value that
|
|
||||||
//! cannot fit in an `INTEGER` (`i64`). Also note that SQLite ignores column
|
|
||||||
//! types, so if you store an `i64` in a column with type `REAL` it will be
|
|
||||||
//! stored as an `INTEGER`, not a `REAL` (unless the column is part of a
|
|
||||||
//! [STRICT table](https://www.sqlite.org/stricttables.html)).
|
|
||||||
//!
|
|
||||||
//! If the `time` feature is enabled, implementations are
|
|
||||||
//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
|
|
||||||
//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
|
|
||||||
//! can be parsed by SQLite's builtin
|
|
||||||
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
|
|
||||||
//! want different storage for datetimes, you can use a newtype.
|
|
||||||
#![cfg_attr(
|
|
||||||
feature = "time",
|
|
||||||
doc = r##"
|
|
||||||
For example, to store datetimes as `i64`s counting the number of seconds since
|
|
||||||
the Unix epoch:
|
|
||||||
|
|
||||||
```
|
|
||||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
|
||||||
use rusqlite::Result;
|
|
||||||
|
|
||||||
pub struct DateTimeSql(pub time::OffsetDateTime);
|
|
||||||
|
|
||||||
impl FromSql for DateTimeSql {
|
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
|
||||||
i64::column_result(value).and_then(|as_i64| {
|
|
||||||
time::OffsetDateTime::from_unix_timestamp(as_i64)
|
|
||||||
.map(|odt| DateTimeSql(odt))
|
|
||||||
.map_err(FromSqlError::other)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for DateTimeSql {
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
|
||||||
Ok(self.0.unix_timestamp().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
"##
|
|
||||||
)]
|
|
||||||
//! [`ToSql`] and [`FromSql`] are also implemented for `Option<T>` where `T`
|
|
||||||
//! implements [`ToSql`] or [`FromSql`] for the cases where you want to know if
|
|
||||||
//! a value was NULL (which gets translated to `None`).
|
|
||||||
|
|
||||||
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
|
|
||||||
pub use self::to_sql::{ToSql, ToSqlOutput};
|
|
||||||
pub use self::value::Value;
|
|
||||||
pub use self::value_ref::ValueRef;
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
#[cfg(feature = "chrono")]
|
|
||||||
mod chrono;
|
|
||||||
mod from_sql;
|
|
||||||
#[cfg(feature = "jiff")]
|
|
||||||
mod jiff;
|
|
||||||
#[cfg(feature = "serde_json")]
|
|
||||||
mod serde_json;
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
mod time;
|
|
||||||
mod to_sql;
|
|
||||||
#[cfg(feature = "url")]
|
|
||||||
mod url;
|
|
||||||
mod value;
|
|
||||||
mod value_ref;
|
|
||||||
|
|
||||||
/// Empty struct that can be used to fill in a query parameter as `NULL`.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
|
||||||
/// # use rusqlite::types::{Null};
|
|
||||||
///
|
|
||||||
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
|
||||||
/// conn.execute("INSERT INTO people (name) VALUES (?1)", [Null])
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Null;
|
|
||||||
|
|
||||||
/// SQLite data types.
|
|
||||||
/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html).
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Type {
|
|
||||||
/// NULL
|
|
||||||
Null,
|
|
||||||
/// 64-bit signed integer
|
|
||||||
Integer,
|
|
||||||
/// 64-bit IEEE floating point number
|
|
||||||
Real,
|
|
||||||
/// String
|
|
||||||
Text,
|
|
||||||
/// BLOB
|
|
||||||
Blob,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Type {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Self::Null => f.pad("Null"),
|
|
||||||
Self::Integer => f.pad("Integer"),
|
|
||||||
Self::Real => f.pad("Real"),
|
|
||||||
Self::Text => f.pad("Text"),
|
|
||||||
Self::Blob => f.pad("Blob"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::Value;
|
|
||||||
use crate::{params, Connection, Error, Result, Statement};
|
|
||||||
use std::ffi::{c_double, c_int};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_blob() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let v1234 = vec![1u8, 2, 3, 4];
|
|
||||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&v1234])?;
|
|
||||||
|
|
||||||
let v: Vec<u8> = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(v, v1234);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_blob() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let empty = vec![];
|
|
||||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&empty])?;
|
|
||||||
|
|
||||||
let v: Vec<u8> = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(v, empty);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_str() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let s = "hello, world!";
|
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [&s])?;
|
|
||||||
|
|
||||||
let from: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(from, s);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_string() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let s = "hello, world!";
|
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [s.to_owned()])?;
|
|
||||||
|
|
||||||
let from: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(from, s);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_value() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo(i) VALUES (?1)", [Value::Integer(10)])?;
|
|
||||||
|
|
||||||
assert_eq!(10, db.one_column::<i64, _>("SELECT i FROM foo", [])?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_option() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let s = "hello, world!";
|
|
||||||
let b = Some(vec![1u8, 2, 3, 4]);
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [Some(s)])?;
|
|
||||||
db.execute("INSERT INTO foo(b) VALUES (?1)", [&b])?;
|
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let row1 = rows.next()?.unwrap();
|
|
||||||
let s1: Option<String> = row1.get_unwrap(0);
|
|
||||||
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
|
|
||||||
assert_eq!(s, s1.unwrap());
|
|
||||||
assert!(b1.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let row2 = rows.next()?.unwrap();
|
|
||||||
let s2: Option<String> = row2.get_unwrap(0);
|
|
||||||
let b2: Option<Vec<u8>> = row2.get_unwrap(1);
|
|
||||||
assert!(s2.is_none());
|
|
||||||
assert_eq!(b, b2);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[expect(clippy::cognitive_complexity)]
|
|
||||||
fn test_mismatched_types() -> Result<()> {
|
|
||||||
fn is_invalid_column_type(err: Error) -> bool {
|
|
||||||
matches!(err, Error::InvalidColumnType(..))
|
|
||||||
}
|
|
||||||
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
|
||||||
[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
|
|
||||||
let row = rows.next()?.unwrap();
|
|
||||||
|
|
||||||
// check the correct types come back as expected
|
|
||||||
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0)?);
|
|
||||||
assert_eq!("text", row.get::<_, String>(1)?);
|
|
||||||
assert_eq!(1, row.get::<_, c_int>(2)?);
|
|
||||||
assert!((1.5 - row.get::<_, c_double>(3)?).abs() < f64::EPSILON);
|
|
||||||
assert_eq!(row.get::<_, Option<c_int>>(4)?, None);
|
|
||||||
assert_eq!(row.get::<_, Option<c_double>>(4)?, None);
|
|
||||||
assert_eq!(row.get::<_, Option<String>>(4)?, None);
|
|
||||||
|
|
||||||
// check some invalid types
|
|
||||||
|
|
||||||
// 0 is actually a blob (Vec<u8>)
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, c_double>(0).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, String>(0).unwrap_err()));
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, time::OffsetDateTime>(0).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Option<c_int>>(0).unwrap_err()
|
|
||||||
));
|
|
||||||
|
|
||||||
// 1 is actually a text (String)
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, c_int>(1).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, c_double>(1).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Vec<u8>>(1).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Option<c_int>>(1).unwrap_err()
|
|
||||||
));
|
|
||||||
|
|
||||||
// 2 is actually an integer
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, String>(2).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Vec<u8>>(2).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Option<String>>(2).unwrap_err()
|
|
||||||
));
|
|
||||||
|
|
||||||
// 3 is actually a float (c_double)
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, c_int>(3).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, String>(3).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Vec<u8>>(3).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Option<c_int>>(3).unwrap_err()
|
|
||||||
));
|
|
||||||
|
|
||||||
// 4 is actually NULL
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, c_int>(4).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, c_double>(4).unwrap_err()
|
|
||||||
));
|
|
||||||
assert!(is_invalid_column_type(row.get::<_, String>(4).unwrap_err()));
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, Vec<u8>>(4).unwrap_err()
|
|
||||||
));
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
assert!(is_invalid_column_type(
|
|
||||||
row.get::<_, time::OffsetDateTime>(4).unwrap_err()
|
|
||||||
));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dynamic_type() -> Result<()> {
|
|
||||||
use super::Value;
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
|
||||||
[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
|
|
||||||
let row = rows.next()?.unwrap();
|
|
||||||
assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0)?);
|
|
||||||
assert_eq!(Value::Text(String::from("text")), row.get::<_, Value>(1)?);
|
|
||||||
assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
|
|
||||||
match row.get::<_, Value>(3)? {
|
|
||||||
Value::Real(val) => assert!((1.5 - val).abs() < f64::EPSILON),
|
|
||||||
x => panic!("Invalid Value {x:?}"),
|
|
||||||
}
|
|
||||||
assert_eq!(Value::Null, row.get::<_, Value>(4)?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! test_conversion {
|
|
||||||
($db_etc:ident, $insert_value:expr, $get_type:ty,expect $expected_value:expr) => {
|
|
||||||
$db_etc.insert_statement.execute(params![$insert_value])?;
|
|
||||||
let res = $db_etc
|
|
||||||
.query_statement
|
|
||||||
.query_row([], |row| row.get::<_, $get_type>(0));
|
|
||||||
assert_eq!(res?, $expected_value);
|
|
||||||
$db_etc.delete_statement.execute([])?;
|
|
||||||
};
|
|
||||||
($db_etc:ident, $insert_value:expr, $get_type:ty,expect_from_sql_error) => {
|
|
||||||
$db_etc.insert_statement.execute(params![$insert_value])?;
|
|
||||||
let res = $db_etc
|
|
||||||
.query_statement
|
|
||||||
.query_row([], |row| row.get::<_, $get_type>(0));
|
|
||||||
res.unwrap_err();
|
|
||||||
$db_etc.delete_statement.execute([])?;
|
|
||||||
};
|
|
||||||
($db_etc:ident, $insert_value:expr, $get_type:ty,expect_to_sql_error) => {
|
|
||||||
$db_etc
|
|
||||||
.insert_statement
|
|
||||||
.execute(params![$insert_value])
|
|
||||||
.unwrap_err();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[expect(clippy::float_cmp)]
|
|
||||||
fn test_numeric_conversions() -> Result<()> {
|
|
||||||
// Test what happens when we store a f32 and retrieve an i32 etc.
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (x)")?;
|
|
||||||
|
|
||||||
// SQLite actually ignores the column types, so we just need to test
|
|
||||||
// different numeric values.
|
|
||||||
|
|
||||||
struct DbEtc<'conn> {
|
|
||||||
insert_statement: Statement<'conn>,
|
|
||||||
query_statement: Statement<'conn>,
|
|
||||||
delete_statement: Statement<'conn>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut db_etc = DbEtc {
|
|
||||||
insert_statement: db.prepare("INSERT INTO foo VALUES (?1)")?,
|
|
||||||
query_statement: db.prepare("SELECT x FROM foo")?,
|
|
||||||
delete_statement: db.prepare("DELETE FROM foo")?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Basic non-converting test.
|
|
||||||
test_conversion!(db_etc, 0u8, u8, expect 0u8);
|
|
||||||
|
|
||||||
// In-range integral conversions.
|
|
||||||
test_conversion!(db_etc, 100u8, i8, expect 100i8);
|
|
||||||
test_conversion!(db_etc, 200u8, u8, expect 200u8);
|
|
||||||
test_conversion!(db_etc, 100u16, i8, expect 100i8);
|
|
||||||
test_conversion!(db_etc, 200u16, u8, expect 200u8);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, u32::MAX, u64, expect u32::MAX as u64);
|
|
||||||
|
|
||||||
test_conversion!(db_etc, i64::MIN, i64, expect i64::MIN);
|
|
||||||
test_conversion!(db_etc, i64::MAX, i64, expect i64::MAX);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, i64::MAX, u64, expect i64::MAX as u64);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, 100usize, usize, expect 100usize);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, 100u64, u64, expect 100u64);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, i64::MAX as u64, u64, expect i64::MAX as u64);
|
|
||||||
|
|
||||||
// Out-of-range integral conversions.
|
|
||||||
test_conversion!(db_etc, 200u8, i8, expect_from_sql_error);
|
|
||||||
test_conversion!(db_etc, 400u16, i8, expect_from_sql_error);
|
|
||||||
test_conversion!(db_etc, 400u16, u8, expect_from_sql_error);
|
|
||||||
test_conversion!(db_etc, -1i8, u8, expect_from_sql_error);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, i64::MIN, u64, expect_from_sql_error);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, u64::MAX, i64, expect_to_sql_error);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, u64::MAX, u64, expect_to_sql_error);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
test_conversion!(db_etc, i64::MAX as u64 + 1, u64, expect_to_sql_error);
|
|
||||||
|
|
||||||
// FromSql integer to float, always works.
|
|
||||||
test_conversion!(db_etc, i64::MIN, f32, expect i64::MIN as f32);
|
|
||||||
test_conversion!(db_etc, i64::MAX, f32, expect i64::MAX as f32);
|
|
||||||
test_conversion!(db_etc, i64::MIN, f64, expect i64::MIN as f64);
|
|
||||||
test_conversion!(db_etc, i64::MAX, f64, expect i64::MAX as f64);
|
|
||||||
|
|
||||||
// FromSql float to int conversion, never works even if the actual value
|
|
||||||
// is an integer.
|
|
||||||
test_conversion!(db_etc, 0f64, i64, expect_from_sql_error);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
133
vendor/rusqlite/src/types/serde_json.rs
vendored
133
vendor/rusqlite/src/types/serde_json.rs
vendored
@@ -1,133 +0,0 @@
|
|||||||
//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
|
|
||||||
|
|
||||||
use serde_json::{Number, Value};
|
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
|
||||||
use crate::{Error, Result};
|
|
||||||
|
|
||||||
/// Serialize JSON `Value` to text:
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// | JSON | SQLite |
|
|
||||||
/// |----------|---------|
|
|
||||||
/// | Null | NULL |
|
|
||||||
/// | Bool | 'true' / 'false' |
|
|
||||||
/// | Number | INT or REAL except u64 |
|
|
||||||
/// | _ | TEXT |
|
|
||||||
impl ToSql for Value {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
match self {
|
|
||||||
Self::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
|
|
||||||
Self::Number(n) if n.is_i64() => Ok(ToSqlOutput::from(n.as_i64().unwrap())),
|
|
||||||
Self::Number(n) if n.is_f64() => Ok(ToSqlOutput::from(n.as_f64().unwrap())),
|
|
||||||
_ => serde_json::to_string(self)
|
|
||||||
.map(ToSqlOutput::from)
|
|
||||||
.map_err(|err| Error::ToSqlConversionFailure(err.into())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize SQLite value to JSON `Value`:
|
|
||||||
///
|
|
||||||
/// | SQLite | JSON |
|
|
||||||
/// |----------|---------|
|
|
||||||
/// | NULL | Null |
|
|
||||||
/// | 'null' | Null |
|
|
||||||
/// | 'true' | Bool |
|
|
||||||
/// | 1 | Number |
|
|
||||||
/// | 0.1 | Number |
|
|
||||||
/// | '"text"' | String |
|
|
||||||
/// | 'text' | _Error_ |
|
|
||||||
/// | '[0, 1]' | Array |
|
|
||||||
/// | '{"x": 1}' | Object |
|
|
||||||
impl FromSql for Value {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
match value {
|
|
||||||
ValueRef::Text(s) => serde_json::from_slice(s), // KO for b"text"
|
|
||||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
|
||||||
ValueRef::Integer(i) => Ok(Self::Number(Number::from(i))),
|
|
||||||
ValueRef::Real(f) => {
|
|
||||||
match Number::from_f64(f) {
|
|
||||||
Some(n) => Ok(Self::Number(n)),
|
|
||||||
_ => return Err(FromSqlError::InvalidType), // FIXME
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ValueRef::Null => Ok(Self::Null),
|
|
||||||
}
|
|
||||||
.map_err(FromSqlError::other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::types::ToSql;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use serde_json::{Number, Value};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_json_value() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let json = r#"{"foo": 13, "bar": "baz"}"#;
|
|
||||||
let data: Value = serde_json::from_str(json).unwrap();
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO foo (t, b) VALUES (?1, ?2)",
|
|
||||||
[&data as &dyn ToSql, &json.as_bytes()],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let t: Value = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(data, t);
|
|
||||||
let b: Value = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(data, b);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_to_sql() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
let v: Option<String> = db.one_column("SELECT ?", [Value::Null])?;
|
|
||||||
assert_eq!(None, v);
|
|
||||||
let v: String = db.one_column("SELECT ?", [Value::Bool(true)])?;
|
|
||||||
assert_eq!("true", v);
|
|
||||||
let v: i64 = db.one_column("SELECT ?", [Value::Number(Number::from(1))])?;
|
|
||||||
assert_eq!(1, v);
|
|
||||||
let v: f64 = db.one_column("SELECT ?", [Value::Number(Number::from_f64(0.1).unwrap())])?;
|
|
||||||
assert_eq!(0.1, v);
|
|
||||||
let v: String = db.one_column("SELECT ?", [Value::String("text".to_owned())])?;
|
|
||||||
assert_eq!("\"text\"", v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_sql() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
let v: Value = db.one_column("SELECT NULL", [])?;
|
|
||||||
assert_eq!(Value::Null, v);
|
|
||||||
let v: Value = db.one_column("SELECT 'null'", [])?;
|
|
||||||
assert_eq!(Value::Null, v);
|
|
||||||
let v: Value = db.one_column("SELECT 'true'", [])?;
|
|
||||||
assert_eq!(Value::Bool(true), v);
|
|
||||||
let v: Value = db.one_column("SELECT 1", [])?;
|
|
||||||
assert_eq!(Value::Number(Number::from(1)), v);
|
|
||||||
let v: Value = db.one_column("SELECT 0.1", [])?;
|
|
||||||
assert_eq!(Value::Number(Number::from_f64(0.1).unwrap()), v);
|
|
||||||
let v: Value = db.one_column("SELECT '\"text\"'", [])?;
|
|
||||||
assert_eq!(Value::String("text".to_owned()), v);
|
|
||||||
let v: Result<Value> = db.one_column("SELECT 'text'", []);
|
|
||||||
assert!(v.is_err());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
429
vendor/rusqlite/src/types/time.rs
vendored
429
vendor/rusqlite/src/types/time.rs
vendored
@@ -1,429 +0,0 @@
|
|||||||
//! Convert formats 1-10 in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) to time types.
|
|
||||||
//! [`ToSql`] and [`FromSql`] implementation for [`OffsetDateTime`].
|
|
||||||
//! [`ToSql`] and [`FromSql`] implementation for [`PrimitiveDateTime`].
|
|
||||||
//! [`ToSql`] and [`FromSql`] implementation for [`Date`].
|
|
||||||
//! [`ToSql`] and [`FromSql`] implementation for [`Time`].
|
|
||||||
//! Time Strings in:
|
|
||||||
//! - Format 2: "YYYY-MM-DD HH:MM"
|
|
||||||
//! - Format 5: "YYYY-MM-DDTHH:MM"
|
|
||||||
//! - Format 8: "HH:MM"
|
|
||||||
//!
|
|
||||||
//! without an explicit second value will assume 0 seconds.
|
|
||||||
//! Time String that contain an optional timezone without an explicit date are unsupported.
|
|
||||||
//! All other assumptions described in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) section are unsupported.
|
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef};
|
|
||||||
use crate::{Error, Result};
|
|
||||||
use time::format_description::FormatItem;
|
|
||||||
use time::macros::format_description;
|
|
||||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
|
||||||
|
|
||||||
const OFFSET_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
|
|
||||||
);
|
|
||||||
const PRIMITIVE_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
|
|
||||||
);
|
|
||||||
const TIME_ENCODING: &[FormatItem<'_>] =
|
|
||||||
format_description!(version = 2, "[hour]:[minute]:[second].[subsecond]");
|
|
||||||
|
|
||||||
const DATE_FORMAT: &[FormatItem<'_>] = format_description!(version = 2, "[year]-[month]-[day]");
|
|
||||||
const TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
|
|
||||||
);
|
|
||||||
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
|
|
||||||
);
|
|
||||||
const UTC_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][optional [Z]]"
|
|
||||||
);
|
|
||||||
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][offset_hour sign:mandatory]:[offset_minute]"
|
|
||||||
);
|
|
||||||
const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
|
||||||
version = 2,
|
|
||||||
"[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
|
|
||||||
);
|
|
||||||
|
|
||||||
/// `OffsetDatetime` => RFC3339 format ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM")
|
|
||||||
impl ToSql for OffsetDateTime {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let time_string = self
|
|
||||||
.format(&OFFSET_DATE_TIME_ENCODING)
|
|
||||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
|
||||||
Ok(ToSqlOutput::from(time_string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports parsing formats 2-7 and 12 (unix timestamp) from https://www.sqlite.org/lang_datefunc.html
|
|
||||||
// Formats 2-7 without a timezone assumes UTC
|
|
||||||
impl FromSql for OffsetDateTime {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
if value.data_type() == Type::Integer {
|
|
||||||
return value
|
|
||||||
.as_i64()
|
|
||||||
.and_then(|i| OffsetDateTime::from_unix_timestamp(i).map_err(FromSqlError::other));
|
|
||||||
}
|
|
||||||
value.as_str().and_then(|s| {
|
|
||||||
if let Some(b' ') = s.as_bytes().get(23) {
|
|
||||||
// legacy
|
|
||||||
return Self::parse(s, &LEGACY_DATE_TIME_FORMAT).map_err(FromSqlError::other);
|
|
||||||
}
|
|
||||||
if s[8..].contains('+') || s[8..].contains('-') {
|
|
||||||
// Formats 2-7 with timezone
|
|
||||||
return Self::parse(s, &OFFSET_DATE_TIME_FORMAT).map_err(FromSqlError::other);
|
|
||||||
}
|
|
||||||
// Formats 2-7 without timezone
|
|
||||||
PrimitiveDateTime::parse(s, &UTC_DATE_TIME_FORMAT)
|
|
||||||
.map(|p| p.assume_utc())
|
|
||||||
.map_err(FromSqlError::other)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
|
||||||
impl ToSql for Date {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_str = self
|
|
||||||
.format(&DATE_FORMAT)
|
|
||||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
|
||||||
Ok(ToSqlOutput::from(date_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
|
||||||
impl FromSql for Date {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().and_then(|s| {
|
|
||||||
Self::parse(s, &DATE_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
|
||||||
impl ToSql for Time {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let time_str = self
|
|
||||||
.format(&TIME_ENCODING)
|
|
||||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
|
||||||
Ok(ToSqlOutput::from(time_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
|
|
||||||
impl FromSql for Time {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().and_then(|s| {
|
|
||||||
Self::parse(s, &TIME_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
|
|
||||||
impl ToSql for PrimitiveDateTime {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
let date_time_str = self
|
|
||||||
.format(&PRIMITIVE_DATE_TIME_ENCODING)
|
|
||||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
|
||||||
Ok(ToSqlOutput::from(date_time_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// YYYY-MM-DD HH:MM
|
|
||||||
/// YYYY-MM-DDTHH:MM
|
|
||||||
/// YYYY-MM-DD HH:MM:SS
|
|
||||||
/// YYYY-MM-DDTHH:MM:SS
|
|
||||||
/// YYYY-MM-DD HH:MM:SS.SSS
|
|
||||||
/// YYYY-MM-DDTHH:MM:SS.SSS
|
|
||||||
/// => ISO 8601 combined date and time with timezone
|
|
||||||
impl FromSql for PrimitiveDateTime {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
value.as_str().and_then(|s| {
|
|
||||||
Self::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
|
|
||||||
.map_err(|err| FromSqlError::Other(err.into()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use time::macros::{date, datetime, time};
|
|
||||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER AS (strftime('%s', t)), b BLOB)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_offset_date_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let mut ts_vec = vec![];
|
|
||||||
|
|
||||||
let make_datetime = |secs: i128, nanos: i128| {
|
|
||||||
OffsetDateTime::from_unix_timestamp_nanos(1_000_000_000 * secs + nanos).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
ts_vec.push(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM
|
|
||||||
ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
|
|
||||||
ts_vec.push(make_datetime(1_500_391_124, 1_000_000)); //July 18, 2017
|
|
||||||
ts_vec.push(make_datetime(2_000_000_000, 2_000_000)); //May 18, 2033
|
|
||||||
ts_vec.push(make_datetime(3_000_000_000, 999_999_999)); //January 24, 2065
|
|
||||||
ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286
|
|
||||||
|
|
||||||
for ts in ts_vec {
|
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?1)", [ts])?;
|
|
||||||
|
|
||||||
let from: OffsetDateTime = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(from, ts);
|
|
||||||
|
|
||||||
let from: OffsetDateTime = db.one_column("SELECT i FROM foo", [])?;
|
|
||||||
assert_eq!(from, ts.truncate_to_second());
|
|
||||||
|
|
||||||
db.execute("DELETE FROM foo", [])?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_offset_date_time_parsing() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let tests = vec![
|
|
||||||
// Rfc3339
|
|
||||||
(
|
|
||||||
"2013-10-07T08:23:19.123456789Z",
|
|
||||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"2013-10-07 08:23:19.123456789Z",
|
|
||||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
|
||||||
),
|
|
||||||
// Format 2
|
|
||||||
("2013-10-07 08:23", datetime!(2013-10-07 8:23 UTC)),
|
|
||||||
("2013-10-07 08:23Z", datetime!(2013-10-07 8:23 UTC)),
|
|
||||||
("2013-10-07 08:23+04:00", datetime!(2013-10-07 8:23 +4)),
|
|
||||||
// Format 3
|
|
||||||
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
|
|
||||||
("2013-10-07 08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
|
|
||||||
(
|
|
||||||
"2013-10-07 08:23:19+04:00",
|
|
||||||
datetime!(2013-10-07 8:23:19 +4),
|
|
||||||
),
|
|
||||||
// Format 4
|
|
||||||
(
|
|
||||||
"2013-10-07 08:23:19.123",
|
|
||||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"2013-10-07 08:23:19.123Z",
|
|
||||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"2013-10-07 08:23:19.123+04:00",
|
|
||||||
datetime!(2013-10-07 8:23:19.123 +4),
|
|
||||||
),
|
|
||||||
// Format 5
|
|
||||||
("2013-10-07T08:23", datetime!(2013-10-07 8:23 UTC)),
|
|
||||||
("2013-10-07T08:23Z", datetime!(2013-10-07 8:23 UTC)),
|
|
||||||
("2013-10-07T08:23+04:00", datetime!(2013-10-07 8:23 +4)),
|
|
||||||
// Format 6
|
|
||||||
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
|
|
||||||
("2013-10-07T08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
|
|
||||||
(
|
|
||||||
"2013-10-07T08:23:19+04:00",
|
|
||||||
datetime!(2013-10-07 8:23:19 +4),
|
|
||||||
),
|
|
||||||
// Format 7
|
|
||||||
(
|
|
||||||
"2013-10-07T08:23:19.123",
|
|
||||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"2013-10-07T08:23:19.123Z",
|
|
||||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"2013-10-07T08:23:19.123+04:00",
|
|
||||||
datetime!(2013-10-07 8:23:19.123 +4),
|
|
||||||
),
|
|
||||||
// Legacy
|
|
||||||
(
|
|
||||||
"2013-10-07 08:23:12:987 -07:00",
|
|
||||||
datetime!(2013-10-07 8:23:12.987 -7),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (s, t) in tests {
|
|
||||||
let result: OffsetDateTime = db.one_column("SELECT ?1", [s])?;
|
|
||||||
assert_eq!(result, t);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let date = date!(2016 - 02 - 23);
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23", s);
|
|
||||||
let t: Date = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(date, t);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let time = time!(23:56:04.00001);
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("23:56:04.00001", s);
|
|
||||||
let v: Time = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(time, v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_primitive_date_time() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let dt = date!(2016 - 02 - 23).with_time(time!(23:56:04));
|
|
||||||
|
|
||||||
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
|
|
||||||
|
|
||||||
let s: String = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!("2016-02-23 23:56:04.0", s);
|
|
||||||
let v: PrimitiveDateTime = db.one_column("SELECT t FROM foo", [])?;
|
|
||||||
assert_eq!(dt, v);
|
|
||||||
|
|
||||||
db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
|
|
||||||
let hms: PrimitiveDateTime = db.one_column("SELECT b FROM foo", [])?;
|
|
||||||
assert_eq!(dt, hms);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_parsing() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let result: Date = db.one_column("SELECT ?1", ["2013-10-07"])?;
|
|
||||||
assert_eq!(result, date!(2013 - 10 - 07));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_time_parsing() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let tests = vec![
|
|
||||||
("08:23", time!(08:23)),
|
|
||||||
("08:23:19", time!(08:23:19)),
|
|
||||||
("08:23:19.111", time!(08:23:19.111)),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (s, t) in tests {
|
|
||||||
let result: Time = db.one_column("SELECT ?1", [s])?;
|
|
||||||
assert_eq!(result, t);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_primitive_date_time_parsing() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
|
|
||||||
let tests = vec![
|
|
||||||
("2013-10-07T08:23", datetime!(2013-10-07 8:23)),
|
|
||||||
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19)),
|
|
||||||
("2013-10-07T08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
|
|
||||||
("2013-10-07 08:23", datetime!(2013-10-07 8:23)),
|
|
||||||
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19)),
|
|
||||||
("2013-10-07 08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (s, t) in tests {
|
|
||||||
let result: PrimitiveDateTime = db.one_column("SELECT ?1", [s])?;
|
|
||||||
assert_eq!(result, t);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sqlite_functions() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
db.one_column::<Time, _>("SELECT CURRENT_TIME", [])?;
|
|
||||||
db.one_column::<Date, _>("SELECT CURRENT_DATE", [])?;
|
|
||||||
db.one_column::<PrimitiveDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
|
||||||
db.one_column::<OffsetDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_time_param() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let now = OffsetDateTime::now_utc().time();
|
|
||||||
let result: Result<bool> = db.one_column(
|
|
||||||
"SELECT 1 WHERE ?1 BETWEEN time('now', '-1 minute') AND time('now', '+1 minute')",
|
|
||||||
[now],
|
|
||||||
);
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_param() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let now = OffsetDateTime::now_utc().date();
|
|
||||||
let result: Result<bool> = db.one_column(
|
|
||||||
"SELECT 1 WHERE ?1 BETWEEN date('now', '-1 day') AND date('now', '+1 day')",
|
|
||||||
[now],
|
|
||||||
);
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_primitive_date_time_param() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let now = PrimitiveDateTime::new(
|
|
||||||
OffsetDateTime::now_utc().date(),
|
|
||||||
OffsetDateTime::now_utc().time(),
|
|
||||||
);
|
|
||||||
let result: Result<bool> = db.one_column(
|
|
||||||
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
|
|
||||||
[now],
|
|
||||||
);
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_offset_date_time_param() -> Result<()> {
|
|
||||||
let db = checked_memory_handle()?;
|
|
||||||
let result: Result<bool> = db.one_column(
|
|
||||||
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
|
|
||||||
[OffsetDateTime::now_utc()],
|
|
||||||
);
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
601
vendor/rusqlite/src/types/to_sql.rs
vendored
601
vendor/rusqlite/src/types/to_sql.rs
vendored
@@ -1,601 +0,0 @@
|
|||||||
use super::{Null, Value, ValueRef};
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
use crate::Error;
|
|
||||||
use crate::Result;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// `ToSqlOutput` represents the possible output types for implementers of the
|
|
||||||
/// [`ToSql`] trait.
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ToSqlOutput<'a> {
|
|
||||||
/// A borrowed SQLite-representable value.
|
|
||||||
Borrowed(ValueRef<'a>),
|
|
||||||
|
|
||||||
/// An owned SQLite-representable value.
|
|
||||||
Owned(Value),
|
|
||||||
|
|
||||||
/// A BLOB of the given length that is filled with
|
|
||||||
/// zeroes.
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
ZeroBlob(i32),
|
|
||||||
|
|
||||||
/// n-th arg of an SQL scalar function
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
Arg(usize),
|
|
||||||
|
|
||||||
/// Pointer passing interface
|
|
||||||
#[cfg(feature = "pointer")]
|
|
||||||
Pointer(
|
|
||||||
(
|
|
||||||
*const std::os::raw::c_void,
|
|
||||||
&'static std::ffi::CStr,
|
|
||||||
Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "pointer")]
|
|
||||||
impl<'a> ToSqlOutput<'a> {
|
|
||||||
/// Pass an `Rc` as a raw pointer to SQLite
|
|
||||||
///
|
|
||||||
/// # Warning
|
|
||||||
/// Leak memory if an error happens before the returned pointer is bound to an SQLite statement.
|
|
||||||
pub fn from_rc<T>(rc: std::rc::Rc<T>, ptr_type: &'static std::ffi::CStr) -> ToSqlOutput<'a> {
|
|
||||||
unsafe extern "C" fn free_rc(p: *mut std::ffi::c_void) {
|
|
||||||
std::rc::Rc::decrement_strong_count(p);
|
|
||||||
}
|
|
||||||
ToSqlOutput::Pointer((
|
|
||||||
std::rc::Rc::into_raw(rc).cast::<std::ffi::c_void>(),
|
|
||||||
ptr_type,
|
|
||||||
Some(free_rc),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
/// Pass a `Box` as a raw pointer to SQLite
|
|
||||||
///
|
|
||||||
/// # Warning
|
|
||||||
/// Leak memory if an error happens before the returned pointer is bound to an SQLite statement.
|
|
||||||
pub fn new_boxed<T>(v: T, ptr_type: &'static std::ffi::CStr) -> ToSqlOutput<'a> {
|
|
||||||
use crate::util::free_boxed_value;
|
|
||||||
|
|
||||||
ToSqlOutput::Pointer((
|
|
||||||
Box::into_raw(Box::new(v)).cast::<std::ffi::c_void>(),
|
|
||||||
ptr_type,
|
|
||||||
Some(free_boxed_value::<T>),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generically allow any type that can be converted into a ValueRef
|
|
||||||
// to be converted into a ToSqlOutput as well.
|
|
||||||
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
|
|
||||||
where
|
|
||||||
&'a T: Into<ValueRef<'a>>,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn from(t: &'a T) -> Self {
|
|
||||||
ToSqlOutput::Borrowed(t.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We cannot also generically allow any type that can be converted
|
|
||||||
// into a Value to be converted into a ToSqlOutput because of
|
|
||||||
// coherence rules (https://github.com/rust-lang/rust/pull/46192),
|
|
||||||
// so we'll manually implement it for all the types we know can
|
|
||||||
// be converted into Values.
|
|
||||||
macro_rules! from_value(
|
|
||||||
($t:ty) => (
|
|
||||||
impl From<$t> for ToSqlOutput<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
(non_zero $t:ty) => (
|
|
||||||
impl From<$t> for ToSqlOutput<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.get().into())}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
from_value!(String);
|
|
||||||
from_value!(Null);
|
|
||||||
from_value!(bool);
|
|
||||||
from_value!(i8);
|
|
||||||
from_value!(i16);
|
|
||||||
from_value!(i32);
|
|
||||||
from_value!(i64);
|
|
||||||
from_value!(isize);
|
|
||||||
from_value!(u8);
|
|
||||||
from_value!(u16);
|
|
||||||
from_value!(u32);
|
|
||||||
from_value!(f32);
|
|
||||||
from_value!(f64);
|
|
||||||
from_value!(Vec<u8>);
|
|
||||||
|
|
||||||
from_value!(non_zero std::num::NonZeroI8);
|
|
||||||
from_value!(non_zero std::num::NonZeroI16);
|
|
||||||
from_value!(non_zero std::num::NonZeroI32);
|
|
||||||
from_value!(non_zero std::num::NonZeroI64);
|
|
||||||
from_value!(non_zero std::num::NonZeroIsize);
|
|
||||||
from_value!(non_zero std::num::NonZeroU8);
|
|
||||||
from_value!(non_zero std::num::NonZeroU16);
|
|
||||||
from_value!(non_zero std::num::NonZeroU32);
|
|
||||||
|
|
||||||
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
|
|
||||||
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
|
|
||||||
// worth adding another case to Value.
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
from_value!(i128);
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
from_value!(non_zero std::num::NonZeroI128);
|
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
from_value!(uuid::Uuid);
|
|
||||||
|
|
||||||
impl ToSql for ToSqlOutput<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(match *self {
|
|
||||||
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
|
|
||||||
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
|
|
||||||
|
|
||||||
#[cfg(feature = "blob")]
|
|
||||||
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
|
|
||||||
#[cfg(feature = "functions")]
|
|
||||||
ToSqlOutput::Arg(i) => ToSqlOutput::Arg(i),
|
|
||||||
#[cfg(feature = "pointer")]
|
|
||||||
ToSqlOutput::Pointer(p) => ToSqlOutput::Pointer(p),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait for types that can be converted into SQLite values. Returns
|
|
||||||
/// [`crate::Error::ToSqlConversionFailure`] if the conversion fails.
|
|
||||||
pub trait ToSql {
|
|
||||||
/// Converts Rust value to SQLite value
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
self.as_ref().to_sql()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToSql + ?Sized> ToSql for Box<T> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
self.as_ref().to_sql()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
self.as_ref().to_sql()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
self.as_ref().to_sql()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should be able to use a generic impl like this:
|
|
||||||
//
|
|
||||||
// impl<T: Copy> ToSql for T where T: Into<Value> {
|
|
||||||
// fn to_sql(&self) -> Result<ToSqlOutput> {
|
|
||||||
// Ok(ToSqlOutput::from((*self).into()))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// instead of the following macro, but this runs afoul of
|
|
||||||
// https://github.com/rust-lang/rust/issues/30191 and reports conflicting
|
|
||||||
// implementations even when there aren't any.
|
|
||||||
|
|
||||||
macro_rules! to_sql_self(
|
|
||||||
($t:ty) => (
|
|
||||||
impl ToSql for $t {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(*self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
to_sql_self!(Null);
|
|
||||||
to_sql_self!(bool);
|
|
||||||
to_sql_self!(i8);
|
|
||||||
to_sql_self!(i16);
|
|
||||||
to_sql_self!(i32);
|
|
||||||
to_sql_self!(i64);
|
|
||||||
to_sql_self!(isize);
|
|
||||||
to_sql_self!(u8);
|
|
||||||
to_sql_self!(u16);
|
|
||||||
to_sql_self!(u32);
|
|
||||||
to_sql_self!(f32);
|
|
||||||
to_sql_self!(f64);
|
|
||||||
|
|
||||||
to_sql_self!(std::num::NonZeroI8);
|
|
||||||
to_sql_self!(std::num::NonZeroI16);
|
|
||||||
to_sql_self!(std::num::NonZeroI32);
|
|
||||||
to_sql_self!(std::num::NonZeroI64);
|
|
||||||
to_sql_self!(std::num::NonZeroIsize);
|
|
||||||
to_sql_self!(std::num::NonZeroU8);
|
|
||||||
to_sql_self!(std::num::NonZeroU16);
|
|
||||||
to_sql_self!(std::num::NonZeroU32);
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
to_sql_self!(i128);
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
to_sql_self!(std::num::NonZeroI128);
|
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
to_sql_self!(uuid::Uuid);
|
|
||||||
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
macro_rules! to_sql_self_fallible(
|
|
||||||
($t:ty) => (
|
|
||||||
impl ToSql for $t {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::Owned(Value::Integer(
|
|
||||||
i64::try_from(*self).map_err(
|
|
||||||
// TODO: Include the values in the error message.
|
|
||||||
|err| Error::ToSqlConversionFailure(err.into())
|
|
||||||
)?
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
(non_zero $t:ty) => (
|
|
||||||
impl ToSql for $t {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::Owned(Value::Integer(
|
|
||||||
i64::try_from(self.get()).map_err(
|
|
||||||
// TODO: Include the values in the error message.
|
|
||||||
|err| Error::ToSqlConversionFailure(err.into())
|
|
||||||
)?
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Special implementations for usize and u64 because these conversions can fail.
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
to_sql_self_fallible!(u64);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
to_sql_self_fallible!(usize);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
to_sql_self_fallible!(non_zero std::num::NonZeroU64);
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
to_sql_self_fallible!(non_zero std::num::NonZeroUsize);
|
|
||||||
|
|
||||||
impl<T: ?Sized> ToSql for &'_ T
|
|
||||||
where
|
|
||||||
T: ToSql,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
(*self).to_sql()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for String {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for str {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Vec<u8> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self.as_slice()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> ToSql for [u8; N] {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(&self[..]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for [u8] {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Value {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToSql> ToSql for Option<T> {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
match *self {
|
|
||||||
None => Ok(ToSqlOutput::from(Null)),
|
|
||||||
Some(ref t) => t.to_sql(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::{ToSql, ToSqlOutput};
|
|
||||||
use crate::{types::Value, types::ValueRef, Result};
|
|
||||||
|
|
||||||
fn is_to_sql<T: ToSql>() {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn to_sql() -> Result<()> {
|
|
||||||
assert_eq!(
|
|
||||||
ToSqlOutput::Borrowed(ValueRef::Null).to_sql()?,
|
|
||||||
ToSqlOutput::Borrowed(ValueRef::Null)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ToSqlOutput::Owned(Value::Null).to_sql()?,
|
|
||||||
ToSqlOutput::Borrowed(ValueRef::Null)
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_integral_types() {
|
|
||||||
is_to_sql::<i8>();
|
|
||||||
is_to_sql::<i16>();
|
|
||||||
is_to_sql::<i32>();
|
|
||||||
is_to_sql::<i64>();
|
|
||||||
is_to_sql::<isize>();
|
|
||||||
is_to_sql::<u8>();
|
|
||||||
is_to_sql::<u16>();
|
|
||||||
is_to_sql::<u32>();
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
is_to_sql::<u64>();
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
is_to_sql::<usize>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_nonzero_types() {
|
|
||||||
is_to_sql::<std::num::NonZeroI8>();
|
|
||||||
is_to_sql::<std::num::NonZeroI16>();
|
|
||||||
is_to_sql::<std::num::NonZeroI32>();
|
|
||||||
is_to_sql::<std::num::NonZeroI64>();
|
|
||||||
is_to_sql::<std::num::NonZeroIsize>();
|
|
||||||
is_to_sql::<std::num::NonZeroU8>();
|
|
||||||
is_to_sql::<std::num::NonZeroU16>();
|
|
||||||
is_to_sql::<std::num::NonZeroU32>();
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
is_to_sql::<std::num::NonZeroU64>();
|
|
||||||
#[cfg(feature = "fallible_uint")]
|
|
||||||
is_to_sql::<std::num::NonZeroUsize>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_u8_array() {
|
|
||||||
let a: [u8; 99] = [0u8; 99];
|
|
||||||
let _a: &[&dyn ToSql] = crate::params![a];
|
|
||||||
let r = ToSql::to_sql(&a);
|
|
||||||
|
|
||||||
r.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cow_str() {
|
|
||||||
use std::borrow::Cow;
|
|
||||||
let s = "str";
|
|
||||||
let cow: Cow<str> = Cow::Borrowed(s);
|
|
||||||
let r = cow.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
let cow: Cow<str> = Cow::Owned::<str>(String::from(s));
|
|
||||||
let r = cow.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
// Ensure this compiles.
|
|
||||||
let _p: &[&dyn ToSql] = crate::params![cow];
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_box_dyn() {
|
|
||||||
let s: Box<dyn ToSql> = Box::new("Hello world!");
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = ToSql::to_sql(&s);
|
|
||||||
|
|
||||||
r.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_box_deref() {
|
|
||||||
let s: Box<str> = "Hello world!".into();
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
|
|
||||||
r.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_box_direct() {
|
|
||||||
let s: Box<str> = "Hello world!".into();
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = ToSql::to_sql(&s);
|
|
||||||
|
|
||||||
r.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cells() {
|
|
||||||
use std::{rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
let source_str: Box<str> = "Hello world!".into();
|
|
||||||
|
|
||||||
let s: Rc<Box<str>> = Rc::new(source_str.clone());
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
|
|
||||||
let s: Arc<Box<str>> = Arc::new(source_str.clone());
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
|
|
||||||
let s: Arc<str> = Arc::from(&*source_str);
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
|
|
||||||
let s: Arc<dyn ToSql> = Arc::new(source_str.clone());
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
|
|
||||||
let s: Rc<str> = Rc::from(&*source_str);
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
|
|
||||||
let s: Rc<dyn ToSql> = Rc::new(source_str);
|
|
||||||
let _s: &[&dyn ToSql] = crate::params![s];
|
|
||||||
let r = s.to_sql();
|
|
||||||
r.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
#[test]
|
|
||||||
fn test_i128() -> Result<()> {
|
|
||||||
use crate::Connection;
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
|
|
||||||
db.execute(
|
|
||||||
"
|
|
||||||
INSERT INTO foo(i128, desc) VALUES
|
|
||||||
(?1, 'zero'),
|
|
||||||
(?2, 'neg one'), (?3, 'neg two'),
|
|
||||||
(?4, 'pos one'), (?5, 'pos two'),
|
|
||||||
(?6, 'min'), (?7, 'max')",
|
|
||||||
[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
|
|
||||||
|
|
||||||
let res = stmt
|
|
||||||
.query_map([], |row| {
|
|
||||||
Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
|
|
||||||
})?
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
res,
|
|
||||||
&[
|
|
||||||
(i128::MIN, "min".to_owned()),
|
|
||||||
(-2, "neg two".to_owned()),
|
|
||||||
(-1, "neg one".to_owned()),
|
|
||||||
(0, "zero".to_owned()),
|
|
||||||
(1, "pos one".to_owned()),
|
|
||||||
(2, "pos two".to_owned()),
|
|
||||||
(i128::MAX, "max".to_owned()),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
#[test]
|
|
||||||
fn test_non_zero_i128() -> Result<()> {
|
|
||||||
use std::num::NonZeroI128;
|
|
||||||
macro_rules! nz {
|
|
||||||
($x:expr) => {
|
|
||||||
NonZeroI128::new($x).unwrap()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let db = crate::Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO foo(i128, desc) VALUES
|
|
||||||
(?1, 'neg one'), (?2, 'neg two'),
|
|
||||||
(?3, 'pos one'), (?4, 'pos two'),
|
|
||||||
(?5, 'min'), (?6, 'max')",
|
|
||||||
[
|
|
||||||
nz!(-1),
|
|
||||||
nz!(-2),
|
|
||||||
nz!(1),
|
|
||||||
nz!(2),
|
|
||||||
nz!(i128::MIN),
|
|
||||||
nz!(i128::MAX),
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
|
|
||||||
|
|
||||||
let res = stmt
|
|
||||||
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
|
|
||||||
.collect::<Result<Vec<(NonZeroI128, String)>, _>>()?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
res,
|
|
||||||
&[
|
|
||||||
(nz!(i128::MIN), "min".to_owned()),
|
|
||||||
(nz!(-2), "neg two".to_owned()),
|
|
||||||
(nz!(-1), "neg one".to_owned()),
|
|
||||||
(nz!(1), "pos one".to_owned()),
|
|
||||||
(nz!(2), "pos two".to_owned()),
|
|
||||||
(nz!(i128::MAX), "max".to_owned()),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
let err = db.query_row("SELECT ?1", [0i128], |row| row.get::<_, NonZeroI128>(0));
|
|
||||||
assert_eq!(err, Err(crate::Error::IntegralValueOutOfRange(0, 0)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
#[test]
|
|
||||||
fn test_uuid() -> Result<()> {
|
|
||||||
use crate::{params, Connection};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")?;
|
|
||||||
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO foo (id, label) VALUES (?1, ?2)",
|
|
||||||
params![id, "target"],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?1")?;
|
|
||||||
|
|
||||||
let mut rows = stmt.query(params![id])?;
|
|
||||||
let row = rows.next()?.unwrap();
|
|
||||||
|
|
||||||
let found_id: Uuid = row.get_unwrap(0);
|
|
||||||
let found_label: String = row.get_unwrap(1);
|
|
||||||
|
|
||||||
assert_eq!(found_id, id);
|
|
||||||
assert_eq!(found_label, "target");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
85
vendor/rusqlite/src/types/url.rs
vendored
85
vendor/rusqlite/src/types/url.rs
vendored
@@ -1,85 +0,0 @@
|
|||||||
//! [`ToSql`] and [`FromSql`] implementation for [`Url`].
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
|
||||||
use crate::Result;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Serialize `Url` to text.
|
|
||||||
impl ToSql for Url {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from(self.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize text to `Url`.
|
|
||||||
impl FromSql for Url {
|
|
||||||
#[inline]
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
match value {
|
|
||||||
ValueRef::Text(s) => {
|
|
||||||
let s = std::str::from_utf8(s).map_err(FromSqlError::other)?;
|
|
||||||
Self::parse(s).map_err(FromSqlError::other)
|
|
||||||
}
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{params, Connection, Error, Result};
|
|
||||||
use url::{ParseError, Url};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Result<Connection> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_url(db: &Connection, id: i64) -> Result<Url> {
|
|
||||||
db.one_column("SELECT v FROM urls WHERE i = ?", [id])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sql_url() -> Result<()> {
|
|
||||||
let db = &checked_memory_handle()?;
|
|
||||||
|
|
||||||
let url0 = Url::parse("http://www.example1.com").unwrap();
|
|
||||||
let url1 = Url::parse("http://www.example1.com/👌").unwrap();
|
|
||||||
let url2 = "http://www.example2.com/👌";
|
|
||||||
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO urls (i, v) VALUES (0, ?1), (1, ?2), (2, ?3), (3, ?4)",
|
|
||||||
// also insert a non-hex encoded url (which might be present if it was
|
|
||||||
// inserted separately)
|
|
||||||
params![url0, url1, url2, "illegal"],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_eq!(get_url(db, 0)?, url0);
|
|
||||||
|
|
||||||
assert_eq!(get_url(db, 1)?, url1);
|
|
||||||
|
|
||||||
// Should successfully read it, even though it wasn't inserted as an
|
|
||||||
// escaped url.
|
|
||||||
let out_url2: Url = get_url(db, 2)?;
|
|
||||||
assert_eq!(out_url2, Url::parse(url2).unwrap());
|
|
||||||
|
|
||||||
// Make sure the conversion error comes through correctly.
|
|
||||||
let err = get_url(db, 3).unwrap_err();
|
|
||||||
match err {
|
|
||||||
Error::FromSqlConversionFailure(_, _, e) => {
|
|
||||||
assert_eq!(
|
|
||||||
*e.downcast::<ParseError>().unwrap(),
|
|
||||||
ParseError::RelativeUrlWithoutBase,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
e => {
|
|
||||||
panic!("Expected conversion failure, got {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
171
vendor/rusqlite/src/types/value.rs
vendored
171
vendor/rusqlite/src/types/value.rs
vendored
@@ -1,171 +0,0 @@
|
|||||||
use super::{Null, Type};
|
|
||||||
|
|
||||||
/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
|
|
||||||
/// dictated by SQLite (not by the caller).
|
|
||||||
///
|
|
||||||
/// See [`ValueRef`](crate::types::ValueRef) for a non-owning dynamic type
|
|
||||||
/// value.
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Value {
|
|
||||||
/// The value is a `NULL` value.
|
|
||||||
Null,
|
|
||||||
/// The value is a signed integer.
|
|
||||||
Integer(i64),
|
|
||||||
/// The value is a floating point number.
|
|
||||||
Real(f64),
|
|
||||||
/// The value is a text string.
|
|
||||||
Text(String),
|
|
||||||
/// The value is a blob of data
|
|
||||||
Blob(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Null> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(_: Null) -> Self {
|
|
||||||
Self::Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(i: bool) -> Self {
|
|
||||||
Self::Integer(i as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<isize> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(i: isize) -> Self {
|
|
||||||
Self::Integer(i as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
|
||||||
impl From<i128> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(i: i128) -> Self {
|
|
||||||
// We store these biased (e.g. with the most significant bit flipped)
|
|
||||||
// so that comparisons with negative numbers work properly.
|
|
||||||
Self::Blob(i128::to_be_bytes(i ^ (1_i128 << 127)).to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
impl From<uuid::Uuid> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(id: uuid::Uuid) -> Self {
|
|
||||||
Self::Blob(id.as_bytes().to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! from_i64(
|
|
||||||
($t:ty) => (
|
|
||||||
impl From<$t> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(i: $t) -> Value {
|
|
||||||
Value::Integer(i64::from(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
from_i64!(i8);
|
|
||||||
from_i64!(i16);
|
|
||||||
from_i64!(i32);
|
|
||||||
from_i64!(u8);
|
|
||||||
from_i64!(u16);
|
|
||||||
from_i64!(u32);
|
|
||||||
|
|
||||||
impl From<i64> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(i: i64) -> Self {
|
|
||||||
Self::Integer(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f32> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(f: f32) -> Self {
|
|
||||||
Self::Real(f.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(f: f64) -> Self {
|
|
||||||
Self::Real(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
Self::Text(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<u8>> for Value {
|
|
||||||
#[inline]
|
|
||||||
fn from(v: Vec<u8>) -> Self {
|
|
||||||
Self::Blob(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<Option<T>> for Value
|
|
||||||
where
|
|
||||||
T: Into<Self>,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn from(v: Option<T>) -> Self {
|
|
||||||
match v {
|
|
||||||
Some(x) => x.into(),
|
|
||||||
None => Self::Null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Value {
|
|
||||||
/// Returns SQLite fundamental datatype.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn data_type(&self) -> Type {
|
|
||||||
match *self {
|
|
||||||
Self::Null => Type::Null,
|
|
||||||
Self::Integer(_) => Type::Integer,
|
|
||||||
Self::Real(_) => Type::Real,
|
|
||||||
Self::Text(_) => Type::Text,
|
|
||||||
Self::Blob(_) => Type::Blob,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::Value;
|
|
||||||
use crate::types::Type;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn from() {
|
|
||||||
assert_eq!(Value::from(2f32), Value::Real(2f64));
|
|
||||||
assert_eq!(Value::from(3.), Value::Real(3.));
|
|
||||||
assert_eq!(Value::from(vec![0u8]), Value::Blob(vec![0u8]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn data_type() {
|
|
||||||
assert_eq!(Value::Null.data_type(), Type::Null);
|
|
||||||
assert_eq!(Value::Integer(0).data_type(), Type::Integer);
|
|
||||||
assert_eq!(Value::Real(0.).data_type(), Type::Real);
|
|
||||||
assert_eq!(Value::Text(String::new()).data_type(), Type::Text);
|
|
||||||
assert_eq!(Value::Blob(vec![]).data_type(), Type::Blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn from_option() {
|
|
||||||
assert_eq!(Value::from(None as Option<i64>), Value::Null);
|
|
||||||
assert_eq!(Value::from(Some(0)), Value::Integer(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
350
vendor/rusqlite/src/types/value_ref.rs
vendored
350
vendor/rusqlite/src/types/value_ref.rs
vendored
@@ -1,350 +0,0 @@
|
|||||||
use super::{Type, Value};
|
|
||||||
use crate::types::{FromSqlError, FromSqlResult};
|
|
||||||
|
|
||||||
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically, the
|
|
||||||
/// memory backing this value is owned by SQLite.
|
|
||||||
///
|
|
||||||
/// See [`Value`](Value) for an owning dynamic type value.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum ValueRef<'a> {
|
|
||||||
/// The value is a `NULL` value.
|
|
||||||
Null,
|
|
||||||
/// The value is a signed integer.
|
|
||||||
Integer(i64),
|
|
||||||
/// The value is a floating point number.
|
|
||||||
Real(f64),
|
|
||||||
/// The value is a text string.
|
|
||||||
Text(&'a [u8]),
|
|
||||||
/// The value is a blob of data
|
|
||||||
Blob(&'a [u8]),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueRef<'_> {
|
|
||||||
/// Returns SQLite fundamental datatype.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn data_type(&self) -> Type {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Null => Type::Null,
|
|
||||||
ValueRef::Integer(_) => Type::Integer,
|
|
||||||
ValueRef::Real(_) => Type::Real,
|
|
||||||
ValueRef::Text(_) => Type::Text,
|
|
||||||
ValueRef::Blob(_) => Type::Blob,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ValueRef<'a> {
|
|
||||||
/// If `self` is case `Integer`, returns the integral value. Otherwise,
|
|
||||||
/// returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Integer(i) => Ok(i),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Null` returns None.
|
|
||||||
/// If `self` is case `Integer`, returns the integral value.
|
|
||||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Null => Ok(None),
|
|
||||||
ValueRef::Integer(i) => Ok(Some(i)),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Real`, returns the floating point value. Otherwise,
|
|
||||||
/// returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Real(f) => Ok(f),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Null` returns None.
|
|
||||||
/// If `self` is case `Real`, returns the floating point value.
|
|
||||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Null => Ok(None),
|
|
||||||
ValueRef::Real(f) => Ok(Some(f)),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Text`, returns the string value. Otherwise, returns
|
|
||||||
/// [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Text(t) => std::str::from_utf8(t).map_err(FromSqlError::Utf8Error),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Null` returns None.
|
|
||||||
/// If `self` is case `Text`, returns the string value.
|
|
||||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Null => Ok(None),
|
|
||||||
ValueRef::Text(t) => std::str::from_utf8(t)
|
|
||||||
.map_err(FromSqlError::Utf8Error)
|
|
||||||
.map(Some),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
|
|
||||||
/// [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Blob(b) => Ok(b),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Null` returns None.
|
|
||||||
/// If `self` is case `Blob`, returns the byte slice.
|
|
||||||
/// Otherwise, returns [`Err(FromSqlError::InvalidType)`](FromSqlError::InvalidType).
|
|
||||||
#[inline]
|
|
||||||
pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Null => Ok(None),
|
|
||||||
ValueRef::Blob(b) => Ok(Some(b)),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the byte slice that makes up this `ValueRef` if it's either
|
|
||||||
/// [`ValueRef::Blob`] or [`ValueRef::Text`].
|
|
||||||
#[inline]
|
|
||||||
pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {
|
|
||||||
match self {
|
|
||||||
ValueRef::Text(s) | ValueRef::Blob(s) => Ok(s),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `self` is case `Null` returns None.
|
|
||||||
/// If `self` is [`ValueRef::Blob`] or [`ValueRef::Text`] returns the byte
|
|
||||||
/// slice that makes up this value
|
|
||||||
#[inline]
|
|
||||||
pub fn as_bytes_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
|
|
||||||
match *self {
|
|
||||||
ValueRef::Null => Ok(None),
|
|
||||||
ValueRef::Text(s) | ValueRef::Blob(s) => Ok(Some(s)),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<ValueRef<'_>> for Value {
|
|
||||||
type Error = FromSqlError;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[track_caller]
|
|
||||||
fn try_from(borrowed: ValueRef<'_>) -> Result<Self, Self::Error> {
|
|
||||||
match borrowed {
|
|
||||||
ValueRef::Null => Ok(Self::Null),
|
|
||||||
ValueRef::Integer(i) => Ok(Self::Integer(i)),
|
|
||||||
ValueRef::Real(r) => Ok(Self::Real(r)),
|
|
||||||
ValueRef::Text(s) => std::str::from_utf8(s)
|
|
||||||
.map(|s| Self::Text(s.to_string()))
|
|
||||||
.map_err(FromSqlError::Utf8Error),
|
|
||||||
ValueRef::Blob(b) => Ok(Self::Blob(b.to_vec())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
|
||||||
#[inline]
|
|
||||||
fn from(s: &str) -> ValueRef<'_> {
|
|
||||||
ValueRef::Text(s.as_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a [u8]> for ValueRef<'a> {
|
|
||||||
#[inline]
|
|
||||||
fn from(s: &[u8]) -> ValueRef<'_> {
|
|
||||||
ValueRef::Blob(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a Value> for ValueRef<'a> {
|
|
||||||
#[inline]
|
|
||||||
fn from(value: &'a Value) -> Self {
|
|
||||||
match *value {
|
|
||||||
Value::Null => ValueRef::Null,
|
|
||||||
Value::Integer(i) => ValueRef::Integer(i),
|
|
||||||
Value::Real(r) => ValueRef::Real(r),
|
|
||||||
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
|
|
||||||
Value::Blob(ref b) => ValueRef::Blob(b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<Option<T>> for ValueRef<'_>
|
|
||||||
where
|
|
||||||
T: Into<Self>,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn from(s: Option<T>) -> Self {
|
|
||||||
match s {
|
|
||||||
Some(x) => x.into(),
|
|
||||||
None => ValueRef::Null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
feature = "functions",
|
|
||||||
feature = "session",
|
|
||||||
feature = "vtab",
|
|
||||||
feature = "preupdate_hook"
|
|
||||||
))]
|
|
||||||
impl ValueRef<'_> {
|
|
||||||
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> Self {
|
|
||||||
use crate::ffi;
|
|
||||||
use std::slice::from_raw_parts;
|
|
||||||
|
|
||||||
match ffi::sqlite3_value_type(value) {
|
|
||||||
ffi::SQLITE_NULL => ValueRef::Null,
|
|
||||||
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
|
|
||||||
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
|
|
||||||
ffi::SQLITE_TEXT => {
|
|
||||||
let text = ffi::sqlite3_value_text(value);
|
|
||||||
let len = ffi::sqlite3_value_bytes(value);
|
|
||||||
assert!(
|
|
||||||
!text.is_null(),
|
|
||||||
"unexpected SQLITE_TEXT value type with NULL data"
|
|
||||||
);
|
|
||||||
let s = from_raw_parts(text.cast::<u8>(), len as usize);
|
|
||||||
ValueRef::Text(s)
|
|
||||||
}
|
|
||||||
ffi::SQLITE_BLOB => {
|
|
||||||
let (blob, len) = (
|
|
||||||
ffi::sqlite3_value_blob(value),
|
|
||||||
ffi::sqlite3_value_bytes(value),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
len >= 0,
|
|
||||||
"unexpected negative return from sqlite3_value_bytes"
|
|
||||||
);
|
|
||||||
if len > 0 {
|
|
||||||
assert!(
|
|
||||||
!blob.is_null(),
|
|
||||||
"unexpected SQLITE_BLOB value type with NULL data"
|
|
||||||
);
|
|
||||||
ValueRef::Blob(from_raw_parts(blob.cast::<u8>(), len as usize))
|
|
||||||
} else {
|
|
||||||
// The return value from sqlite3_value_blob() for a zero-length BLOB
|
|
||||||
// is a NULL pointer.
|
|
||||||
ValueRef::Blob(&[])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!("sqlite3_value_type returned invalid value"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO sqlite3_value_frombind // 3.28.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::ValueRef;
|
|
||||||
use crate::types::FromSqlResult;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn as_i64() -> FromSqlResult<()> {
|
|
||||||
assert!(ValueRef::Real(1.0).as_i64().is_err());
|
|
||||||
assert_eq!(ValueRef::Integer(1).as_i64(), Ok(1));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_i64_or_null() -> FromSqlResult<()> {
|
|
||||||
assert_eq!(ValueRef::Null.as_i64_or_null(), Ok(None));
|
|
||||||
assert!(ValueRef::Real(1.0).as_i64_or_null().is_err());
|
|
||||||
assert_eq!(ValueRef::Integer(1).as_i64_or_null(), Ok(Some(1)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_f64() -> FromSqlResult<()> {
|
|
||||||
assert!(ValueRef::Integer(1).as_f64().is_err());
|
|
||||||
assert_eq!(ValueRef::Real(1.0).as_f64(), Ok(1.0));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_f64_or_null() -> FromSqlResult<()> {
|
|
||||||
assert_eq!(ValueRef::Null.as_f64_or_null(), Ok(None));
|
|
||||||
assert!(ValueRef::Integer(1).as_f64_or_null().is_err());
|
|
||||||
assert_eq!(ValueRef::Real(1.0).as_f64_or_null(), Ok(Some(1.0)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_str() -> FromSqlResult<()> {
|
|
||||||
assert!(ValueRef::Null.as_str().is_err());
|
|
||||||
assert_eq!(ValueRef::Text(b"").as_str(), Ok(""));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_str_or_null() -> FromSqlResult<()> {
|
|
||||||
assert_eq!(ValueRef::Null.as_str_or_null(), Ok(None));
|
|
||||||
assert!(ValueRef::Integer(1).as_str_or_null().is_err());
|
|
||||||
assert_eq!(ValueRef::Text(b"").as_str_or_null(), Ok(Some("")));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_blob() -> FromSqlResult<()> {
|
|
||||||
assert!(ValueRef::Null.as_blob().is_err());
|
|
||||||
assert_eq!(ValueRef::Blob(b"").as_blob(), Ok(&b""[..]));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_blob_or_null() -> FromSqlResult<()> {
|
|
||||||
assert_eq!(ValueRef::Null.as_blob_or_null(), Ok(None));
|
|
||||||
assert!(ValueRef::Integer(1).as_blob_or_null().is_err());
|
|
||||||
assert_eq!(ValueRef::Blob(b"").as_blob_or_null(), Ok(Some(&b""[..])));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_bytes() -> FromSqlResult<()> {
|
|
||||||
assert!(ValueRef::Null.as_bytes().is_err());
|
|
||||||
assert_eq!(ValueRef::Blob(b"").as_bytes(), Ok(&b""[..]));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn as_bytes_or_null() -> FromSqlResult<()> {
|
|
||||||
assert_eq!(ValueRef::Null.as_bytes_or_null(), Ok(None));
|
|
||||||
assert!(ValueRef::Integer(1).as_bytes_or_null().is_err());
|
|
||||||
assert_eq!(ValueRef::Blob(b"").as_bytes_or_null(), Ok(Some(&b""[..])));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn from_value() {
|
|
||||||
use crate::types::Value;
|
|
||||||
assert_eq!(
|
|
||||||
ValueRef::from(&Value::Text("".to_owned())),
|
|
||||||
ValueRef::Text(b"")
|
|
||||||
);
|
|
||||||
assert_eq!(ValueRef::from(&Value::Blob(vec![])), ValueRef::Blob(b""));
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn from_option() {
|
|
||||||
assert_eq!(ValueRef::from(None as Option<&str>), ValueRef::Null);
|
|
||||||
assert_eq!(ValueRef::from(Some("")), ValueRef::Text(b""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
121
vendor/rusqlite/src/unlock_notify.rs
vendored
121
vendor/rusqlite/src/unlock_notify.rs
vendored
@@ -1,121 +0,0 @@
|
|||||||
//! [Unlock Notification](http://sqlite.org/unlock_notify.html)
|
|
||||||
|
|
||||||
use std::ffi::{c_int, c_void};
|
|
||||||
use std::panic::catch_unwind;
|
|
||||||
use std::sync::{Condvar, Mutex};
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
|
|
||||||
struct UnlockNotification {
|
|
||||||
cond: Condvar, // Condition variable to wait on
|
|
||||||
mutex: Mutex<bool>, // Mutex to protect structure
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnlockNotification {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
cond: Condvar::new(),
|
|
||||||
mutex: Mutex::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fired(&self) {
|
|
||||||
let mut flag = unpoison(self.mutex.lock());
|
|
||||||
*flag = true;
|
|
||||||
self.cond.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wait(&self) {
|
|
||||||
let mut fired = unpoison(self.mutex.lock());
|
|
||||||
while !*fired {
|
|
||||||
fired = unpoison(self.cond.wait(fired));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unpoison<T>(r: Result<T, std::sync::PoisonError<T>>) -> T {
|
|
||||||
r.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function is an unlock-notify callback
|
|
||||||
unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
|
|
||||||
use std::slice::from_raw_parts;
|
|
||||||
let args = from_raw_parts(ap_arg as *const &UnlockNotification, n_arg as usize);
|
|
||||||
for un in args {
|
|
||||||
drop(catch_unwind(std::panic::AssertUnwindSafe(|| un.fired())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
|
|
||||||
rc == ffi::SQLITE_LOCKED_SHAREDCACHE
|
|
||||||
|| (rc & 0xFF) == ffi::SQLITE_LOCKED
|
|
||||||
&& ffi::sqlite3_extended_errcode(db) == ffi::SQLITE_LOCKED_SHAREDCACHE
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()`
|
|
||||||
/// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the
|
|
||||||
/// associated database connection.
|
|
||||||
///
|
|
||||||
/// This function calls `sqlite3_unlock_notify()` to register for an
|
|
||||||
/// unlock-notify callback, then blocks until that callback is delivered
|
|
||||||
/// and returns `SQLITE_OK`. The caller should then retry the failed operation.
|
|
||||||
///
|
|
||||||
/// Or, if `sqlite3_unlock_notify()` indicates that to block would deadlock
|
|
||||||
/// the system, then this function returns `SQLITE_LOCKED` immediately. In
|
|
||||||
/// this case the caller should not retry the operation and should roll
|
|
||||||
/// back the current transaction (if any).
|
|
||||||
#[cfg(feature = "unlock_notify")]
|
|
||||||
pub unsafe fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int {
|
|
||||||
let un = UnlockNotification::new();
|
|
||||||
/* Register for an unlock-notify callback. */
|
|
||||||
let rc = ffi::sqlite3_unlock_notify(
|
|
||||||
db,
|
|
||||||
Some(unlock_notify_cb),
|
|
||||||
&un as *const UnlockNotification as *mut c_void,
|
|
||||||
);
|
|
||||||
debug_assert!(
|
|
||||||
rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_LOCKED_SHAREDCACHE || rc == ffi::SQLITE_OK
|
|
||||||
);
|
|
||||||
if rc == ffi::SQLITE_OK {
|
|
||||||
un.wait();
|
|
||||||
}
|
|
||||||
rc
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior};
|
|
||||||
use std::sync::mpsc::sync_channel;
|
|
||||||
use std::thread;
|
|
||||||
use std::time;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
all(target_family = "wasm", target_os = "unknown"),
|
|
||||||
ignore = "no thread on this platform"
|
|
||||||
)]
|
|
||||||
#[test]
|
|
||||||
fn test_unlock_notify() -> Result<()> {
|
|
||||||
let url = "file::memory:?cache=shared";
|
|
||||||
let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI;
|
|
||||||
let db1 = Connection::open_with_flags(url, flags)?;
|
|
||||||
db1.execute_batch("CREATE TABLE foo (x)")?;
|
|
||||||
let (rx, tx) = sync_channel(0);
|
|
||||||
let child = thread::spawn(move || {
|
|
||||||
let mut db2 = Connection::open_with_flags(url, flags).unwrap();
|
|
||||||
let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap();
|
|
||||||
tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap();
|
|
||||||
rx.send(1).unwrap();
|
|
||||||
let ten_millis = time::Duration::from_millis(10);
|
|
||||||
thread::sleep(ten_millis);
|
|
||||||
tx2.commit().unwrap();
|
|
||||||
});
|
|
||||||
assert_eq!(tx.recv().unwrap(), 1);
|
|
||||||
assert_eq!(42, db1.one_column::<i64, _>("SELECT x FROM foo", [])?);
|
|
||||||
child.join().unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
55
vendor/rusqlite/src/util/mod.rs
vendored
55
vendor/rusqlite/src/util/mod.rs
vendored
@@ -1,55 +0,0 @@
|
|||||||
// Internal utilities
|
|
||||||
pub(crate) mod param_cache;
|
|
||||||
mod small_cstr;
|
|
||||||
pub(crate) use param_cache::ParamIndexCache;
|
|
||||||
pub(crate) use small_cstr::SmallCString;
|
|
||||||
|
|
||||||
// Doesn't use any modern features or vtab stuff, but is only used by them.
|
|
||||||
mod sqlite_string;
|
|
||||||
pub(crate) use sqlite_string::{alloc, SqliteMallocString};
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
feature = "collation",
|
|
||||||
feature = "functions",
|
|
||||||
feature = "vtab",
|
|
||||||
feature = "pointer"
|
|
||||||
))]
|
|
||||||
pub(crate) unsafe extern "C" fn free_boxed_value<T>(p: *mut std::ffi::c_void) {
|
|
||||||
drop(Box::from_raw(p.cast::<T>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
pub enum Named<'a> {
|
|
||||||
Small(SmallCString),
|
|
||||||
C(&'a CStr),
|
|
||||||
}
|
|
||||||
impl std::ops::Deref for Named<'_> {
|
|
||||||
type Target = CStr;
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &CStr {
|
|
||||||
match self {
|
|
||||||
Named::Small(s) => s.as_cstr(),
|
|
||||||
Named::C(s) => s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Database, table, column, collation, function, module, vfs name
|
|
||||||
pub trait Name: std::fmt::Debug {
|
|
||||||
/// As C string
|
|
||||||
fn as_cstr(&self) -> Result<Named<'_>>;
|
|
||||||
}
|
|
||||||
impl Name for &str {
|
|
||||||
fn as_cstr(&self) -> Result<Named<'_>> {
|
|
||||||
let ss = SmallCString::new(self)?;
|
|
||||||
Ok(Named::Small(ss))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Name for &CStr {
|
|
||||||
#[inline]
|
|
||||||
fn as_cstr(&self) -> Result<Named<'_>> {
|
|
||||||
Ok(Named::C(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
vendor/rusqlite/src/util/param_cache.rs
vendored
63
vendor/rusqlite/src/util/param_cache.rs
vendored
@@ -1,63 +0,0 @@
|
|||||||
use super::SmallCString;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
/// Maps parameter names to parameter indices.
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
// BTreeMap seems to do better here unless we want to pull in a custom hash
|
|
||||||
// function.
|
|
||||||
pub(crate) struct ParamIndexCache(RefCell<BTreeMap<SmallCString, usize>>);
|
|
||||||
|
|
||||||
impl ParamIndexCache {
|
|
||||||
pub fn get_or_insert_with<F>(&self, s: &str, func: F) -> Option<usize>
|
|
||||||
where
|
|
||||||
F: FnOnce(&std::ffi::CStr) -> Option<usize>,
|
|
||||||
{
|
|
||||||
let mut cache = self.0.borrow_mut();
|
|
||||||
// Avoid entry API, needs allocation to test membership.
|
|
||||||
if let Some(v) = cache.get(s) {
|
|
||||||
return Some(*v);
|
|
||||||
}
|
|
||||||
// If there's an internal nul in the name it couldn't have been a
|
|
||||||
// parameter, so early return here is ok.
|
|
||||||
let name = SmallCString::new(s).ok()?;
|
|
||||||
let val = func(&name)?;
|
|
||||||
cache.insert(name, val);
|
|
||||||
Some(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_cache() {
|
|
||||||
let p = ParamIndexCache::default();
|
|
||||||
let v = p.get_or_insert_with("foo", |cstr| {
|
|
||||||
assert_eq!(cstr.to_str().unwrap(), "foo");
|
|
||||||
Some(3)
|
|
||||||
});
|
|
||||||
assert_eq!(v, Some(3));
|
|
||||||
let v = p.get_or_insert_with("foo", |_| {
|
|
||||||
panic!("shouldn't be called this time");
|
|
||||||
});
|
|
||||||
assert_eq!(v, Some(3));
|
|
||||||
let v = p.get_or_insert_with("gar\0bage", |_| {
|
|
||||||
panic!("shouldn't be called here either");
|
|
||||||
});
|
|
||||||
assert_eq!(v, None);
|
|
||||||
let v = p.get_or_insert_with("bar", |cstr| {
|
|
||||||
assert_eq!(cstr.to_str().unwrap(), "bar");
|
|
||||||
None
|
|
||||||
});
|
|
||||||
assert_eq!(v, None);
|
|
||||||
let v = p.get_or_insert_with("bar", |cstr| {
|
|
||||||
assert_eq!(cstr.to_str().unwrap(), "bar");
|
|
||||||
Some(30)
|
|
||||||
});
|
|
||||||
assert_eq!(v, Some(30));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
159
vendor/rusqlite/src/util/small_cstr.rs
vendored
159
vendor/rusqlite/src/util/small_cstr.rs
vendored
@@ -1,159 +0,0 @@
|
|||||||
use smallvec::{smallvec, SmallVec};
|
|
||||||
use std::ffi::{CStr, CString, NulError};
|
|
||||||
|
|
||||||
/// Similar to `std::ffi::CString`, but avoids heap allocating if the string is
|
|
||||||
/// small enough. Also guarantees it's input is UTF-8 -- used for cases where we
|
|
||||||
/// need to pass a NUL-terminated string to SQLite, and we have a `&str`.
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct SmallCString(SmallVec<[u8; 16]>);
|
|
||||||
|
|
||||||
impl SmallCString {
|
|
||||||
#[inline]
|
|
||||||
pub fn new(s: &str) -> Result<Self, NulError> {
|
|
||||||
if s.as_bytes().contains(&0_u8) {
|
|
||||||
return Err(Self::fabricate_nul_error(s));
|
|
||||||
}
|
|
||||||
let mut buf = SmallVec::with_capacity(s.len() + 1);
|
|
||||||
buf.extend_from_slice(s.as_bytes());
|
|
||||||
buf.push(0);
|
|
||||||
let res = Self(buf);
|
|
||||||
res.debug_checks();
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
self.debug_checks();
|
|
||||||
// Constructor takes a &str so this is safe.
|
|
||||||
unsafe { std::str::from_utf8_unchecked(self.as_bytes_without_nul()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the bytes not including the NUL terminator. E.g. the bytes which
|
|
||||||
/// make up our `str`:
|
|
||||||
/// - `SmallCString::new("foo").as_bytes_without_nul() == b"foo"`
|
|
||||||
/// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"`
|
|
||||||
#[inline]
|
|
||||||
pub fn as_bytes_without_nul(&self) -> &[u8] {
|
|
||||||
self.debug_checks();
|
|
||||||
&self.0[..self.len()]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the bytes behind this str *including* the NUL terminator. This
|
|
||||||
/// should never return an empty slice.
|
|
||||||
#[inline]
|
|
||||||
pub fn as_bytes_with_nul(&self) -> &[u8] {
|
|
||||||
self.debug_checks();
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
fn debug_checks(&self) {
|
|
||||||
debug_assert_ne!(self.0.len(), 0);
|
|
||||||
debug_assert_eq!(self.0[self.0.len() - 1], 0);
|
|
||||||
let strbytes = &self.0[..(self.0.len() - 1)];
|
|
||||||
debug_assert!(!strbytes.contains(&0));
|
|
||||||
debug_assert!(std::str::from_utf8(strbytes).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
fn debug_checks(&self) {}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
debug_assert_ne!(self.0.len(), 0);
|
|
||||||
self.0.len() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[allow(unused)] // clippy wants this function.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.len() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn as_cstr(&self) -> &CStr {
|
|
||||||
let bytes = self.as_bytes_with_nul();
|
|
||||||
debug_assert!(CStr::from_bytes_with_nul(bytes).is_ok());
|
|
||||||
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn fabricate_nul_error(b: &str) -> NulError {
|
|
||||||
CString::new(b).unwrap_err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SmallCString {
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(smallvec![0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for SmallCString {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_tuple("SmallCString").field(&self.as_str()).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for SmallCString {
|
|
||||||
type Target = CStr;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &CStr {
|
|
||||||
self.as_cstr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::borrow::Borrow<str> for SmallCString {
|
|
||||||
#[inline]
|
|
||||||
fn borrow(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_small_cstring() {
|
|
||||||
// We don't go through the normal machinery for default, so make sure
|
|
||||||
// things work.
|
|
||||||
assert_eq!(SmallCString::default().0, SmallCString::new("").unwrap().0);
|
|
||||||
assert_eq!(SmallCString::new("foo").unwrap().len(), 3);
|
|
||||||
assert_eq!(
|
|
||||||
SmallCString::new("foo").unwrap().as_bytes_with_nul(),
|
|
||||||
b"foo\0"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
SmallCString::new("foo").unwrap().as_bytes_without_nul(),
|
|
||||||
b"foo",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(SmallCString::new("😀").unwrap().len(), 4);
|
|
||||||
assert_eq!(
|
|
||||||
SmallCString::new("😀").unwrap().0.as_slice(),
|
|
||||||
b"\xf0\x9f\x98\x80\0",
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
SmallCString::new("😀").unwrap().as_bytes_without_nul(),
|
|
||||||
b"\xf0\x9f\x98\x80",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(SmallCString::new("").unwrap().len(), 0);
|
|
||||||
assert!(SmallCString::new("").unwrap().is_empty());
|
|
||||||
|
|
||||||
assert_eq!(SmallCString::new("").unwrap().0.as_slice(), b"\0");
|
|
||||||
assert_eq!(SmallCString::new("").unwrap().as_bytes_without_nul(), b"");
|
|
||||||
|
|
||||||
SmallCString::new("\0").unwrap_err();
|
|
||||||
SmallCString::new("\0abc").unwrap_err();
|
|
||||||
SmallCString::new("abc\0").unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
230
vendor/rusqlite/src/util/sqlite_string.rs
vendored
230
vendor/rusqlite/src/util/sqlite_string.rs
vendored
@@ -1,230 +0,0 @@
|
|||||||
// This is used when either vtab or modern-sqlite is on. Different methods are
|
|
||||||
// used in each feature. Avoid having to track this for each function. We will
|
|
||||||
// still warn for anything that's not used by either, though.
|
|
||||||
#![cfg_attr(not(feature = "vtab"), allow(dead_code))]
|
|
||||||
use crate::ffi;
|
|
||||||
use std::ffi::{c_char, CStr};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::ptr::NonNull;
|
|
||||||
|
|
||||||
// Space to hold this string must be obtained
|
|
||||||
// from an SQLite memory allocation function
|
|
||||||
pub(crate) fn alloc(s: &str) -> *mut c_char {
|
|
||||||
SqliteMallocString::from_str(s).into_raw()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A string we own that's allocated on the SQLite heap. Automatically calls
|
|
||||||
/// `sqlite3_free` when dropped, unless `into_raw` (or `into_inner`) is called
|
|
||||||
/// on it. If constructed from a rust string, `sqlite3_malloc64` is used.
|
|
||||||
///
|
|
||||||
/// It has identical representation to a nonnull `*mut c_char`, so you can use
|
|
||||||
/// it transparently as one. It's nonnull, so Option<SqliteMallocString> can be
|
|
||||||
/// used for nullable ones (it's still just one pointer).
|
|
||||||
///
|
|
||||||
/// Most strings shouldn't use this! Only places where the string needs to be
|
|
||||||
/// freed with `sqlite3_free`. This includes `sqlite3_extended_sql` results,
|
|
||||||
/// some error message pointers... Note that misuse is extremely dangerous!
|
|
||||||
///
|
|
||||||
/// Note that this is *not* a lossless interface. Incoming strings with internal
|
|
||||||
/// NULs are modified, and outgoing strings which are non-UTF8 are modified.
|
|
||||||
/// This seems unavoidable -- it tries very hard to not panic.
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub(crate) struct SqliteMallocString {
|
|
||||||
ptr: NonNull<c_char>,
|
|
||||||
_boo: PhantomData<Box<[c_char]>>,
|
|
||||||
}
|
|
||||||
// This is owned data for a primitive type, and thus it's safe to implement
|
|
||||||
// these. That said, nothing needs them, and they make things easier to misuse.
|
|
||||||
|
|
||||||
// unsafe impl Send for SqliteMallocString {}
|
|
||||||
// unsafe impl Sync for SqliteMallocString {}
|
|
||||||
|
|
||||||
impl SqliteMallocString {
|
|
||||||
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
|
|
||||||
/// allocated by `sqlite3_malloc64`, and that SQLite expects us to free it!
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn from_raw_nonnull(ptr: NonNull<c_char>) -> Self {
|
|
||||||
Self {
|
|
||||||
ptr,
|
|
||||||
_boo: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
|
|
||||||
/// allocated by `sqlite3_malloc64`, and that SQLite expects us to free it!
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn from_raw(ptr: *mut c_char) -> Option<Self> {
|
|
||||||
NonNull::new(ptr).map(|p| Self::from_raw_nonnull(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the pointer behind `self`. After this is called, we no longer manage
|
|
||||||
/// it.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn into_inner(self) -> NonNull<c_char> {
|
|
||||||
let p = self.ptr;
|
|
||||||
std::mem::forget(self);
|
|
||||||
p
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the pointer behind `self`. After this is called, we no longer manage
|
|
||||||
/// it.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn into_raw(self) -> *mut c_char {
|
|
||||||
self.into_inner().as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrow the pointer behind `self`. We still manage it when this function
|
|
||||||
/// returns. If you want to relinquish ownership, use `into_raw`.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn as_ptr(&self) -> *const c_char {
|
|
||||||
self.ptr.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn as_cstr(&self) -> &CStr {
|
|
||||||
unsafe { CStr::from_ptr(self.as_ptr()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
|
|
||||||
self.as_cstr().to_string_lossy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert `s` into a SQLite string.
|
|
||||||
///
|
|
||||||
/// This should almost never be done except for cases like error messages or
|
|
||||||
/// other strings that SQLite frees.
|
|
||||||
///
|
|
||||||
/// If `s` contains internal NULs, we'll replace them with
|
|
||||||
/// `NUL_REPLACE_CHAR`.
|
|
||||||
///
|
|
||||||
/// Except for `debug_assert`s which may trigger during testing, this
|
|
||||||
/// function never panics. If we hit integer overflow or the allocation
|
|
||||||
/// fails, we call `handle_alloc_error` which aborts the program after
|
|
||||||
/// calling a global hook.
|
|
||||||
///
|
|
||||||
/// This means it's safe to use in extern "C" functions even outside
|
|
||||||
/// `catch_unwind`.
|
|
||||||
pub(crate) fn from_str(s: &str) -> Self {
|
|
||||||
let s = if s.as_bytes().contains(&0) {
|
|
||||||
std::borrow::Cow::Owned(make_nonnull(s))
|
|
||||||
} else {
|
|
||||||
std::borrow::Cow::Borrowed(s)
|
|
||||||
};
|
|
||||||
debug_assert!(!s.as_bytes().contains(&0));
|
|
||||||
let bytes: &[u8] = s.as_ref().as_bytes();
|
|
||||||
let src_ptr: *const c_char = bytes.as_ptr().cast();
|
|
||||||
let src_len = bytes.len();
|
|
||||||
let maybe_len_plus_1 = s.len().checked_add(1).and_then(|v| v.try_into().ok());
|
|
||||||
unsafe {
|
|
||||||
let res_ptr = maybe_len_plus_1
|
|
||||||
.and_then(|len_to_alloc| {
|
|
||||||
// `>` because we added 1.
|
|
||||||
debug_assert!(len_to_alloc > 0);
|
|
||||||
debug_assert_eq!((len_to_alloc - 1) as usize, src_len);
|
|
||||||
NonNull::new(ffi::sqlite3_malloc64(len_to_alloc).cast::<c_char>())
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
use std::alloc::{handle_alloc_error, Layout};
|
|
||||||
// Report via handle_alloc_error so that it can be handled with any
|
|
||||||
// other allocation errors and properly diagnosed.
|
|
||||||
//
|
|
||||||
// This is safe:
|
|
||||||
// - `align` is never 0
|
|
||||||
// - `align` is always a power of 2.
|
|
||||||
// - `size` needs no realignment because it's guaranteed to be aligned
|
|
||||||
// (everything is aligned to 1)
|
|
||||||
// - `size` is also never zero, although this function doesn't actually require
|
|
||||||
// it now.
|
|
||||||
let len = s.len().saturating_add(1).min(isize::MAX as usize);
|
|
||||||
let layout = Layout::from_size_align_unchecked(len, 1);
|
|
||||||
// Note: This call does not return.
|
|
||||||
handle_alloc_error(layout);
|
|
||||||
});
|
|
||||||
let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
|
|
||||||
src_ptr.copy_to_nonoverlapping(buf, src_len);
|
|
||||||
buf.add(src_len).write(0);
|
|
||||||
debug_assert_eq!(CStr::from_ptr(res_ptr.as_ptr()).to_bytes(), bytes);
|
|
||||||
Self::from_raw_nonnull(res_ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const NUL_REPLACE: &str = "␀";
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn make_nonnull(v: &str) -> String {
|
|
||||||
v.replace('\0', NUL_REPLACE)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for SqliteMallocString {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { ffi::sqlite3_free(self.ptr.as_ptr().cast()) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_from_str() {
|
|
||||||
let to_check = [
|
|
||||||
("", ""),
|
|
||||||
("\0", "␀"),
|
|
||||||
("␀", "␀"),
|
|
||||||
("\0bar", "␀bar"),
|
|
||||||
("foo\0bar", "foo␀bar"),
|
|
||||||
("foo\0", "foo␀"),
|
|
||||||
("a\0b\0c\0\0d", "a␀b␀c␀␀d"),
|
|
||||||
("foobar0123", "foobar0123"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for &(input, output) in &to_check {
|
|
||||||
let s = SqliteMallocString::from_str(input);
|
|
||||||
assert_eq!(s.to_string_lossy(), output);
|
|
||||||
assert_eq!(s.as_cstr().to_str().unwrap(), output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will trigger an asan error if into_raw still freed the ptr.
|
|
||||||
#[test]
|
|
||||||
fn test_lossy() {
|
|
||||||
let p = SqliteMallocString::from_str("abcd").into_raw();
|
|
||||||
// Make invalid
|
|
||||||
let s = unsafe {
|
|
||||||
p.cast::<u8>().write(b'\xff');
|
|
||||||
SqliteMallocString::from_raw(p).unwrap()
|
|
||||||
};
|
|
||||||
assert_eq!(s.to_string_lossy().as_ref(), "\u{FFFD}bcd");
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will trigger an asan error if into_raw still freed the ptr.
|
|
||||||
#[test]
|
|
||||||
fn test_into_raw() {
|
|
||||||
let mut v = vec![];
|
|
||||||
for i in 0..1000 {
|
|
||||||
v.push(SqliteMallocString::from_str(&i.to_string()).into_raw());
|
|
||||||
v.push(SqliteMallocString::from_str(&format!("abc {i} 😀")).into_raw());
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
for (i, s) in v.chunks_mut(2).enumerate() {
|
|
||||||
let s0 = std::mem::replace(&mut s[0], std::ptr::null_mut());
|
|
||||||
let s1 = std::mem::replace(&mut s[1], std::ptr::null_mut());
|
|
||||||
assert_eq!(CStr::from_ptr(s0).to_str().unwrap(), &i.to_string());
|
|
||||||
assert_eq!(CStr::from_ptr(s1).to_str().unwrap(), &format!("abc {i} 😀"));
|
|
||||||
let _ = SqliteMallocString::from_raw(s0).unwrap();
|
|
||||||
let _ = SqliteMallocString::from_raw(s1).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_alloc() {
|
|
||||||
let err = alloc("error");
|
|
||||||
unsafe { ffi::sqlite3_free(err.cast()) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
27
vendor/rusqlite/src/version.rs
vendored
27
vendor/rusqlite/src/version.rs
vendored
@@ -1,27 +0,0 @@
|
|||||||
use crate::ffi;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
/// Returns the SQLite version as an integer; e.g., `3016002` for version
|
|
||||||
/// 3.16.2.
|
|
||||||
///
|
|
||||||
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn version_number() -> i32 {
|
|
||||||
unsafe { ffi::sqlite3_libversion_number() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
|
|
||||||
///
|
|
||||||
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics when version is not valid UTF-8.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn version() -> &'static str {
|
|
||||||
let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
|
|
||||||
cstr.to_str()
|
|
||||||
.expect("SQLite version string is not valid UTF8 ?!")
|
|
||||||
}
|
|
||||||
221
vendor/rusqlite/src/vtab/array.rs
vendored
221
vendor/rusqlite/src/vtab/array.rs
vendored
@@ -1,221 +0,0 @@
|
|||||||
//! Array Virtual Table.
|
|
||||||
//!
|
|
||||||
//! Note: `rarray`, not `carray` is the name of the table valued function we
|
|
||||||
//! define.
|
|
||||||
//!
|
|
||||||
//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c)
|
|
||||||
//! C extension: `https://www.sqlite.org/carray.html`
|
|
||||||
//!
|
|
||||||
//! # Example
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! # use rusqlite::{types::Value, Connection, Result, params};
|
|
||||||
//! # use std::rc::Rc;
|
|
||||||
//! fn example(db: &Connection) -> Result<()> {
|
|
||||||
//! // Note: This should be done once (usually when opening the DB).
|
|
||||||
//! rusqlite::vtab::array::load_module(&db)?;
|
|
||||||
//! let v = [1i64, 2, 3, 4];
|
|
||||||
//! // Note: A `Rc<Vec<Value>>` must be used as the parameter.
|
|
||||||
//! let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
|
|
||||||
//! let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
|
|
||||||
//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
|
|
||||||
//! for value in rows {
|
|
||||||
//! println!("{}", value?);
|
|
||||||
//! }
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use std::ffi::{c_int, CStr};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::types::{ToSql, ToSqlOutput, Value};
|
|
||||||
use crate::vtab::{
|
|
||||||
eponymous_only_module, Context, Filters, IndexConstraintOp, IndexInfo, VTab, VTabConnection,
|
|
||||||
VTabCursor,
|
|
||||||
};
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
// http://sqlite.org/bindptr.html
|
|
||||||
|
|
||||||
const ARRAY_TYPE: &CStr = c"rarray";
|
|
||||||
|
|
||||||
/// Array parameter / pointer
|
|
||||||
pub type Array = Rc<Vec<Value>>;
|
|
||||||
|
|
||||||
impl ToSql for Array {
|
|
||||||
#[inline]
|
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(ToSqlOutput::from_rc(self.clone(), ARRAY_TYPE))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register the "rarray" module.
|
|
||||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
|
||||||
let aux: Option<()> = None;
|
|
||||||
conn.create_module(c"rarray", eponymous_only_module::<ArrayTab>(), aux)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Column numbers
|
|
||||||
// const CARRAY_COLUMN_VALUE : c_int = 0;
|
|
||||||
const CARRAY_COLUMN_POINTER: c_int = 1;
|
|
||||||
|
|
||||||
/// An instance of the Array virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct ArrayTab {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
|
|
||||||
type Aux = ();
|
|
||||||
type Cursor = ArrayTabCursor<'vtab>;
|
|
||||||
|
|
||||||
fn connect(
|
|
||||||
_: &mut VTabConnection,
|
|
||||||
_aux: Option<&()>,
|
|
||||||
_args: &[&[u8]],
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
let vtab = Self {
|
|
||||||
base: ffi::sqlite3_vtab::default(),
|
|
||||||
};
|
|
||||||
Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
|
||||||
// Index of the pointer= constraint
|
|
||||||
let mut ptr_idx = false;
|
|
||||||
for (constraint, mut constraint_usage) in info.constraints_and_usages() {
|
|
||||||
if !constraint.is_usable() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let CARRAY_COLUMN_POINTER = constraint.column() {
|
|
||||||
ptr_idx = true;
|
|
||||||
constraint_usage.set_argv_index(1);
|
|
||||||
constraint_usage.set_omit(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ptr_idx {
|
|
||||||
info.set_estimated_cost(1_f64);
|
|
||||||
info.set_estimated_rows(100);
|
|
||||||
info.set_idx_num(1);
|
|
||||||
} else {
|
|
||||||
info.set_estimated_cost(2_147_483_647_f64);
|
|
||||||
info.set_estimated_rows(2_147_483_647);
|
|
||||||
info.set_idx_num(0);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
|
|
||||||
Ok(ArrayTabCursor::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cursor for the Array virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct ArrayTabCursor<'vtab> {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab_cursor,
|
|
||||||
/// The rowid
|
|
||||||
row_id: i64,
|
|
||||||
/// Pointer to the array of values ("pointer")
|
|
||||||
ptr: Option<&'vtab Vec<Value>>,
|
|
||||||
phantom: PhantomData<&'vtab ArrayTab>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrayTabCursor<'_> {
|
|
||||||
fn new<'vtab>() -> ArrayTabCursor<'vtab> {
|
|
||||||
ArrayTabCursor {
|
|
||||||
base: ffi::sqlite3_vtab_cursor::default(),
|
|
||||||
row_id: 0,
|
|
||||||
ptr: None,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn len(&self) -> i64 {
|
|
||||||
match self.ptr {
|
|
||||||
Some(a) => a.len() as i64,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe impl VTabCursor for ArrayTabCursor<'_> {
|
|
||||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
|
||||||
if idx_num > 0 {
|
|
||||||
self.ptr = unsafe { args.get_pointer(0, ARRAY_TYPE) };
|
|
||||||
} else {
|
|
||||||
self.ptr = None;
|
|
||||||
}
|
|
||||||
self.row_id = 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> Result<()> {
|
|
||||||
self.row_id += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eof(&self) -> bool {
|
|
||||||
self.row_id > self.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
|
||||||
match i {
|
|
||||||
CARRAY_COLUMN_POINTER => Ok(()),
|
|
||||||
_ => {
|
|
||||||
if let Some(array) = self.ptr {
|
|
||||||
let value = &array[(self.row_id - 1) as usize];
|
|
||||||
ctx.set_result(&value)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rowid(&self) -> Result<i64> {
|
|
||||||
Ok(self.row_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::types::Value;
|
|
||||||
use crate::vtab::array;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_array_module() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
array::load_module(&db)?;
|
|
||||||
|
|
||||||
let v = vec![1i64, 2, 3, 4];
|
|
||||||
let values: Vec<Value> = v.into_iter().map(Value::from).collect();
|
|
||||||
let ptr = Rc::new(values);
|
|
||||||
{
|
|
||||||
let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
|
|
||||||
|
|
||||||
let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?;
|
|
||||||
assert_eq!(2, Rc::strong_count(&ptr));
|
|
||||||
let mut count = 0;
|
|
||||||
for (i, value) in rows.enumerate() {
|
|
||||||
assert_eq!(i as i64, value? - 1);
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
assert_eq!(4, count);
|
|
||||||
}
|
|
||||||
assert_eq!(1, Rc::strong_count(&ptr));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
400
vendor/rusqlite/src/vtab/csvtab.rs
vendored
400
vendor/rusqlite/src/vtab/csvtab.rs
vendored
@@ -1,400 +0,0 @@
|
|||||||
//! CSV Virtual Table.
|
|
||||||
//!
|
|
||||||
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C
|
|
||||||
//! extension: `https://www.sqlite.org/csv.html`
|
|
||||||
//!
|
|
||||||
//! # Example
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! # use rusqlite::{Connection, Result};
|
|
||||||
//! fn example() -> Result<()> {
|
|
||||||
//! // Note: This should be done once (usually when opening the DB).
|
|
||||||
//! let db = Connection::open_in_memory()?;
|
|
||||||
//! rusqlite::vtab::csvtab::load_module(&db)?;
|
|
||||||
//! // Assume my_csv.csv
|
|
||||||
//! let schema = "
|
|
||||||
//! CREATE VIRTUAL TABLE my_csv_data
|
|
||||||
//! USING csv(filename = 'my_csv.csv')
|
|
||||||
//! ";
|
|
||||||
//! db.execute_batch(schema)?;
|
|
||||||
//! // Now the `my_csv_data` (virtual) table can be queried as normal...
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::types::Null;
|
|
||||||
use crate::vtab::{
|
|
||||||
escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, Filters, IndexInfo,
|
|
||||||
VTab, VTabConfig, VTabConnection, VTabCursor, VTabKind,
|
|
||||||
};
|
|
||||||
use crate::{Connection, Error, Result};
|
|
||||||
|
|
||||||
/// Register the "csv" module.
|
|
||||||
/// ```sql
|
|
||||||
/// CREATE VIRTUAL TABLE vtab USING csv(
|
|
||||||
/// filename=FILENAME -- Name of file containing CSV content
|
|
||||||
/// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);'
|
|
||||||
/// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no".
|
|
||||||
/// [, columns=N] -- Assume the CSV file contains N columns.
|
|
||||||
/// [, delimiter=C] -- CSV delimiter. Default ','.
|
|
||||||
/// [, quote=C] -- CSV quote. Default '"'. 0 means no quote.
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
|
||||||
let aux: Option<()> = None;
|
|
||||||
conn.create_module(c"csv", read_only_module::<CsvTab>(), aux)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An instance of the CSV virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct CsvTab {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab,
|
|
||||||
/// Name of the CSV file
|
|
||||||
filename: String,
|
|
||||||
has_headers: bool,
|
|
||||||
delimiter: u8,
|
|
||||||
quote: u8,
|
|
||||||
/// Offset to start of data
|
|
||||||
offset_first_row: csv::Position,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CsvTab {
|
|
||||||
fn reader(&self) -> Result<csv::Reader<File>, csv::Error> {
|
|
||||||
csv::ReaderBuilder::new()
|
|
||||||
.has_headers(self.has_headers)
|
|
||||||
.delimiter(self.delimiter)
|
|
||||||
.quote(self.quote)
|
|
||||||
.from_path(&self.filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_byte(arg: &str) -> Option<u8> {
|
|
||||||
if arg.len() == 1 {
|
|
||||||
arg.bytes().next()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|
||||||
type Aux = ();
|
|
||||||
type Cursor = CsvTabCursor<'vtab>;
|
|
||||||
|
|
||||||
fn connect(
|
|
||||||
db: &mut VTabConnection,
|
|
||||||
_aux: Option<&()>,
|
|
||||||
args: &[&[u8]],
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
if args.len() < 4 {
|
|
||||||
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut vtab = Self {
|
|
||||||
base: ffi::sqlite3_vtab::default(),
|
|
||||||
filename: String::new(),
|
|
||||||
has_headers: false,
|
|
||||||
delimiter: b',',
|
|
||||||
quote: b'"',
|
|
||||||
offset_first_row: csv::Position::new(),
|
|
||||||
};
|
|
||||||
let mut schema = None;
|
|
||||||
let mut n_col = None;
|
|
||||||
|
|
||||||
let args = &args[3..];
|
|
||||||
for c_slice in args {
|
|
||||||
let (param, value) = super::parameter(c_slice)?;
|
|
||||||
match param {
|
|
||||||
"filename" => {
|
|
||||||
if !Path::new(value).exists() {
|
|
||||||
return Err(Error::ModuleError(format!("file '{value}' does not exist")));
|
|
||||||
}
|
|
||||||
value.clone_into(&mut vtab.filename);
|
|
||||||
}
|
|
||||||
"schema" => {
|
|
||||||
schema = Some(value.to_owned());
|
|
||||||
}
|
|
||||||
"columns" => {
|
|
||||||
if let Ok(n) = value.parse::<u16>() {
|
|
||||||
if n_col.is_some() {
|
|
||||||
return Err(Error::ModuleError(
|
|
||||||
"more than one 'columns' parameter".to_owned(),
|
|
||||||
));
|
|
||||||
} else if n == 0 {
|
|
||||||
return Err(Error::ModuleError(
|
|
||||||
"must have at least one column".to_owned(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
n_col = Some(n);
|
|
||||||
} else {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"unrecognized argument to 'columns': {value}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"header" => {
|
|
||||||
if let Some(b) = parse_boolean(value) {
|
|
||||||
vtab.has_headers = b;
|
|
||||||
} else {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"unrecognized argument to 'header': {value}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"delimiter" => {
|
|
||||||
if let Some(b) = Self::parse_byte(value) {
|
|
||||||
vtab.delimiter = b;
|
|
||||||
} else {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"unrecognized argument to 'delimiter': {value}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"quote" => {
|
|
||||||
if let Some(b) = Self::parse_byte(value) {
|
|
||||||
if b == b'0' {
|
|
||||||
vtab.quote = 0;
|
|
||||||
} else {
|
|
||||||
vtab.quote = b;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"unrecognized argument to 'quote': {value}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"unrecognized parameter '{param}'"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if vtab.filename.is_empty() {
|
|
||||||
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cols: Vec<String> = Vec::new();
|
|
||||||
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
|
|
||||||
let mut reader = vtab.reader()?;
|
|
||||||
if vtab.has_headers {
|
|
||||||
{
|
|
||||||
let headers = reader.headers()?;
|
|
||||||
// headers ignored if cols is not empty
|
|
||||||
if n_col.is_none() && schema.is_none() {
|
|
||||||
cols = headers
|
|
||||||
.into_iter()
|
|
||||||
.map(|header| escape_double_quote(header).into_owned())
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vtab.offset_first_row = reader.position().clone();
|
|
||||||
} else {
|
|
||||||
let mut record = csv::ByteRecord::new();
|
|
||||||
if reader.read_byte_record(&mut record)? {
|
|
||||||
for (i, _) in record.iter().enumerate() {
|
|
||||||
cols.push(format!("c{i}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(n_col) = n_col {
|
|
||||||
for i in 0..n_col {
|
|
||||||
cols.push(format!("c{i}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cols.is_empty() && schema.is_none() {
|
|
||||||
return Err(Error::ModuleError("no column specified".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if schema.is_none() {
|
|
||||||
let mut sql = String::from("CREATE TABLE x(");
|
|
||||||
for (i, col) in cols.iter().enumerate() {
|
|
||||||
sql.push('"');
|
|
||||||
sql.push_str(col);
|
|
||||||
sql.push_str("\" TEXT");
|
|
||||||
if i == cols.len() - 1 {
|
|
||||||
sql.push_str(");");
|
|
||||||
} else {
|
|
||||||
sql.push_str(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schema = Some(sql);
|
|
||||||
}
|
|
||||||
db.config(VTabConfig::DirectOnly)?;
|
|
||||||
Ok((schema.unwrap(), vtab))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only a forward full table scan is supported.
|
|
||||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
|
||||||
info.set_estimated_cost(1_000_000.);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self) -> Result<CsvTabCursor<'_>> {
|
|
||||||
Ok(CsvTabCursor::new(self.reader()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CreateVTab<'_> for CsvTab {
|
|
||||||
const KIND: VTabKind = VTabKind::Default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cursor for the CSV virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct CsvTabCursor<'vtab> {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab_cursor,
|
|
||||||
/// The CSV reader object
|
|
||||||
reader: csv::Reader<File>,
|
|
||||||
/// Current cursor position used as rowid
|
|
||||||
row_number: usize,
|
|
||||||
/// Values of the current row
|
|
||||||
cols: csv::StringRecord,
|
|
||||||
eof: bool,
|
|
||||||
phantom: PhantomData<&'vtab CsvTab>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CsvTabCursor<'_> {
|
|
||||||
fn new<'vtab>(reader: csv::Reader<File>) -> CsvTabCursor<'vtab> {
|
|
||||||
CsvTabCursor {
|
|
||||||
base: ffi::sqlite3_vtab_cursor::default(),
|
|
||||||
reader,
|
|
||||||
row_number: 0,
|
|
||||||
cols: csv::StringRecord::new(),
|
|
||||||
eof: false,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accessor to the associated virtual table.
|
|
||||||
fn vtab(&self) -> &CsvTab {
|
|
||||||
unsafe { &*(self.base.pVtab as *const CsvTab) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl VTabCursor for CsvTabCursor<'_> {
|
|
||||||
// Only a full table scan is supported. So `filter` simply rewinds to
|
|
||||||
// the beginning.
|
|
||||||
fn filter(
|
|
||||||
&mut self,
|
|
||||||
_idx_num: c_int,
|
|
||||||
_idx_str: Option<&str>,
|
|
||||||
_args: &Filters<'_>,
|
|
||||||
) -> Result<()> {
|
|
||||||
{
|
|
||||||
let offset_first_row = self.vtab().offset_first_row.clone();
|
|
||||||
self.reader.seek(offset_first_row)?;
|
|
||||||
}
|
|
||||||
self.row_number = 0;
|
|
||||||
self.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> Result<()> {
|
|
||||||
{
|
|
||||||
self.eof = self.reader.is_done();
|
|
||||||
if self.eof {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.eof = !self.reader.read_record(&mut self.cols)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.row_number += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eof(&self) -> bool {
|
|
||||||
self.eof
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
|
|
||||||
if col < 0 || col as usize >= self.cols.len() {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"column index out of bounds: {col}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if self.cols.is_empty() {
|
|
||||||
return ctx.set_result(&Null);
|
|
||||||
}
|
|
||||||
// TODO Affinity
|
|
||||||
ctx.set_result(&self.cols[col as usize].to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rowid(&self) -> Result<i64> {
|
|
||||||
Ok(self.row_number as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<csv::Error> for Error {
|
|
||||||
#[cold]
|
|
||||||
fn from(err: csv::Error) -> Self {
|
|
||||||
Self::ModuleError(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::vtab::csvtab;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
all(target_family = "wasm", target_os = "unknown"),
|
|
||||||
ignore = "no filesystem on this platform"
|
|
||||||
)]
|
|
||||||
#[test]
|
|
||||||
fn test_csv_module() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
csvtab::load_module(&db)?;
|
|
||||||
db.execute_batch(
|
|
||||||
"CREATE VIRTUAL TABLE vtab USING csv(filename = 'test.csv', header = yes)",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut s = db.prepare("SELECT rowid, * FROM vtab")?;
|
|
||||||
{
|
|
||||||
let headers = s.column_names();
|
|
||||||
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ids: Result<Vec<i32>> = s.query([])?.map(|row| row.get::<_, i32>(0)).collect();
|
|
||||||
let sum = ids?.iter().sum::<i32>();
|
|
||||||
assert_eq!(sum, 15);
|
|
||||||
}
|
|
||||||
db.execute_batch("DROP TABLE vtab")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
all(target_family = "wasm", target_os = "unknown"),
|
|
||||||
ignore = "no filesystem on this platform"
|
|
||||||
)]
|
|
||||||
#[test]
|
|
||||||
fn test_csv_cursor() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
csvtab::load_module(&db)?;
|
|
||||||
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut s = db.prepare(
|
|
||||||
"SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
|
|
||||||
v1.rowid < v2.rowid",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut rows = s.query([])?;
|
|
||||||
let row = rows.next()?.unwrap();
|
|
||||||
assert_eq!(row.get_unwrap::<_, i32>(0), 2);
|
|
||||||
}
|
|
||||||
db.execute_batch("DROP TABLE vtab")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1574
vendor/rusqlite/src/vtab/mod.rs
vendored
1574
vendor/rusqlite/src/vtab/mod.rs
vendored
File diff suppressed because it is too large
Load Diff
344
vendor/rusqlite/src/vtab/series.rs
vendored
344
vendor/rusqlite/src/vtab/series.rs
vendored
@@ -1,344 +0,0 @@
|
|||||||
//! Generate series virtual table.
|
|
||||||
//!
|
|
||||||
//! Port of C [generate series
|
|
||||||
//! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c):
|
|
||||||
//! `https://www.sqlite.org/series.html`
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::types::Type;
|
|
||||||
use crate::vtab::{
|
|
||||||
eponymous_only_module, Context, Filters, IndexConstraintOp, IndexInfo, VTab, VTabConfig,
|
|
||||||
VTabConnection, VTabCursor,
|
|
||||||
};
|
|
||||||
use crate::{error::error_from_sqlite_code, Connection, Result};
|
|
||||||
|
|
||||||
/// Register the `generate_series` module.
|
|
||||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
|
||||||
let aux: Option<()> = None;
|
|
||||||
conn.create_module(
|
|
||||||
c"generate_series",
|
|
||||||
eponymous_only_module::<SeriesTab>(),
|
|
||||||
aux,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Column numbers
|
|
||||||
// const SERIES_COLUMN_VALUE : c_int = 0;
|
|
||||||
const SERIES_COLUMN_START: c_int = 1;
|
|
||||||
const SERIES_COLUMN_STOP: c_int = 2;
|
|
||||||
const SERIES_COLUMN_STEP: c_int = 3;
|
|
||||||
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct QueryPlanFlags: c_int {
|
|
||||||
// start = $value -- constraint exists
|
|
||||||
const START = 1;
|
|
||||||
// stop = $value -- constraint exists
|
|
||||||
const STOP = 2;
|
|
||||||
// step = $value -- constraint exists
|
|
||||||
const STEP = 4;
|
|
||||||
// output in descending order
|
|
||||||
const DESC = 8;
|
|
||||||
// output in ascending order
|
|
||||||
const ASC = 16;
|
|
||||||
// Both start and stop
|
|
||||||
const BOTH = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An instance of the Series virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct SeriesTab {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
|
||||||
type Aux = ();
|
|
||||||
type Cursor = SeriesTabCursor<'vtab>;
|
|
||||||
|
|
||||||
fn connect(
|
|
||||||
db: &mut VTabConnection,
|
|
||||||
_aux: Option<&()>,
|
|
||||||
_args: &[&[u8]],
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
let vtab = Self {
|
|
||||||
base: ffi::sqlite3_vtab::default(),
|
|
||||||
};
|
|
||||||
db.config(VTabConfig::Innocuous)?;
|
|
||||||
Ok((
|
|
||||||
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
|
|
||||||
vtab,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
|
||||||
// The query plan bitmask
|
|
||||||
let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty();
|
|
||||||
// Mask of unusable constraints
|
|
||||||
let mut unusable_mask: QueryPlanFlags = QueryPlanFlags::empty();
|
|
||||||
// Constraints on start, stop, and step
|
|
||||||
let mut a_idx: [Option<usize>; 3] = [None, None, None];
|
|
||||||
for (i, constraint) in info.constraints().enumerate() {
|
|
||||||
if constraint.column() < SERIES_COLUMN_START {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let (i_col, i_mask) = match constraint.column() {
|
|
||||||
SERIES_COLUMN_START => (0, QueryPlanFlags::START),
|
|
||||||
SERIES_COLUMN_STOP => (1, QueryPlanFlags::STOP),
|
|
||||||
SERIES_COLUMN_STEP => (2, QueryPlanFlags::STEP),
|
|
||||||
_ => {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !constraint.is_usable() {
|
|
||||||
unusable_mask |= i_mask;
|
|
||||||
} else if constraint.operator() == IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
|
|
||||||
idx_num |= i_mask;
|
|
||||||
a_idx[i_col] = Some(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Number of arguments that SeriesTabCursor::filter expects
|
|
||||||
let mut n_arg = 0;
|
|
||||||
for j in a_idx.iter().flatten() {
|
|
||||||
n_arg += 1;
|
|
||||||
let mut constraint_usage = info.constraint_usage(*j);
|
|
||||||
constraint_usage.set_argv_index(n_arg);
|
|
||||||
constraint_usage.set_omit(true);
|
|
||||||
#[cfg(all(test, feature = "modern_sqlite"))]
|
|
||||||
debug_assert_eq!(Ok("BINARY"), info.collation(*j));
|
|
||||||
}
|
|
||||||
if !(unusable_mask & !idx_num).is_empty() {
|
|
||||||
return Err(error_from_sqlite_code(ffi::SQLITE_CONSTRAINT, None));
|
|
||||||
}
|
|
||||||
if idx_num.contains(QueryPlanFlags::BOTH) {
|
|
||||||
// Both start= and stop= boundaries are available.
|
|
||||||
#[expect(clippy::bool_to_int_with_if)]
|
|
||||||
info.set_estimated_cost(f64::from(
|
|
||||||
2 - if idx_num.contains(QueryPlanFlags::STEP) {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
|
||||||
));
|
|
||||||
info.set_estimated_rows(1000);
|
|
||||||
let order_by_consumed = {
|
|
||||||
let mut order_bys = info.order_bys();
|
|
||||||
if let Some(order_by) = order_bys.next() {
|
|
||||||
if order_by.column() == 0 {
|
|
||||||
if order_by.is_order_by_desc() {
|
|
||||||
idx_num |= QueryPlanFlags::DESC;
|
|
||||||
} else {
|
|
||||||
idx_num |= QueryPlanFlags::ASC;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if order_by_consumed {
|
|
||||||
info.set_order_by_consumed(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If either boundary is missing, we have to generate a huge span
|
|
||||||
// of numbers. Make this case very expensive so that the query
|
|
||||||
// planner will work hard to avoid it.
|
|
||||||
info.set_estimated_rows(2_147_483_647);
|
|
||||||
}
|
|
||||||
info.set_idx_num(idx_num.bits());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self) -> Result<SeriesTabCursor<'_>> {
|
|
||||||
Ok(SeriesTabCursor::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cursor for the Series virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct SeriesTabCursor<'vtab> {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab_cursor,
|
|
||||||
/// True to count down rather than up
|
|
||||||
is_desc: bool,
|
|
||||||
/// The rowid
|
|
||||||
row_id: i64,
|
|
||||||
/// Current value ("value")
|
|
||||||
value: i64,
|
|
||||||
/// Minimum value ("start")
|
|
||||||
min_value: i64,
|
|
||||||
/// Maximum value ("stop")
|
|
||||||
max_value: i64,
|
|
||||||
/// Increment ("step")
|
|
||||||
step: i64,
|
|
||||||
phantom: PhantomData<&'vtab SeriesTab>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeriesTabCursor<'_> {
|
|
||||||
fn new<'vtab>() -> SeriesTabCursor<'vtab> {
|
|
||||||
SeriesTabCursor {
|
|
||||||
base: ffi::sqlite3_vtab_cursor::default(),
|
|
||||||
is_desc: false,
|
|
||||||
row_id: 0,
|
|
||||||
value: 0,
|
|
||||||
min_value: 0,
|
|
||||||
max_value: 0,
|
|
||||||
step: 0,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl VTabCursor for SeriesTabCursor<'_> {
|
|
||||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
|
||||||
let mut idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
|
|
||||||
let mut i = 0;
|
|
||||||
if idx_num.contains(QueryPlanFlags::START) {
|
|
||||||
self.min_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
self.min_value = 0;
|
|
||||||
}
|
|
||||||
if idx_num.contains(QueryPlanFlags::STOP) {
|
|
||||||
self.max_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
self.max_value = 0xffff_ffff;
|
|
||||||
}
|
|
||||||
if idx_num.contains(QueryPlanFlags::STEP) {
|
|
||||||
self.step = args.get::<Option<_>>(i)?.unwrap_or_default();
|
|
||||||
if self.step == 0 {
|
|
||||||
self.step = 1;
|
|
||||||
} else if self.step < 0 {
|
|
||||||
self.step = -self.step;
|
|
||||||
if !idx_num.contains(QueryPlanFlags::ASC) {
|
|
||||||
idx_num |= QueryPlanFlags::DESC;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.step = 1;
|
|
||||||
};
|
|
||||||
for arg in args.iter() {
|
|
||||||
if arg.data_type() == Type::Null {
|
|
||||||
// If any of the constraints have a NULL value, then return no rows.
|
|
||||||
self.min_value = 1;
|
|
||||||
self.max_value = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.is_desc = idx_num.contains(QueryPlanFlags::DESC);
|
|
||||||
if self.is_desc {
|
|
||||||
self.value = self.max_value;
|
|
||||||
if self.step > 0 {
|
|
||||||
self.value -= (self.max_value - self.min_value) % self.step;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.value = self.min_value;
|
|
||||||
}
|
|
||||||
self.row_id = 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> Result<()> {
|
|
||||||
if self.is_desc {
|
|
||||||
self.value -= self.step;
|
|
||||||
} else {
|
|
||||||
self.value += self.step;
|
|
||||||
}
|
|
||||||
self.row_id += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eof(&self) -> bool {
|
|
||||||
if self.is_desc {
|
|
||||||
self.value < self.min_value
|
|
||||||
} else {
|
|
||||||
self.value > self.max_value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
|
||||||
let x = match i {
|
|
||||||
SERIES_COLUMN_START => self.min_value,
|
|
||||||
SERIES_COLUMN_STOP => self.max_value,
|
|
||||||
SERIES_COLUMN_STEP => self.step,
|
|
||||||
_ => self.value,
|
|
||||||
};
|
|
||||||
ctx.set_result(&x)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rowid(&self) -> Result<i64> {
|
|
||||||
Ok(self.row_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
use crate::vtab::series;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_series_module() -> Result<()> {
|
|
||||||
let version = unsafe { ffi::sqlite3_libversion_number() };
|
|
||||||
if version < 3_008_012 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
series::load_module(&db)?;
|
|
||||||
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)")?;
|
|
||||||
|
|
||||||
let series = s.query_map([], |row| row.get::<_, i32>(0))?;
|
|
||||||
|
|
||||||
let mut expected = 0;
|
|
||||||
for value in series {
|
|
||||||
assert_eq!(expected, value?);
|
|
||||||
expected += 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut s =
|
|
||||||
db.prepare("SELECT * FROM generate_series WHERE start=1 AND stop=9 AND step=2")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(vec![1, 3, 5, 7, 9], series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series LIMIT 5")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(vec![0, 1, 2, 3, 4], series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(0,32,5) ORDER BY value DESC")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series);
|
|
||||||
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL)")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
let empty = Vec::<i32>::new();
|
|
||||||
assert_eq!(empty, series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL)")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(empty, series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(5,10,NULL)")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(empty, series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL,10,2)")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(empty, series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL,2)")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(empty, series);
|
|
||||||
let mut s = db.prepare("SELECT * FROM generate_series(NULL) ORDER BY value DESC")?;
|
|
||||||
let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
|
||||||
assert_eq!(empty, series);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
385
vendor/rusqlite/src/vtab/vtablog.rs
vendored
385
vendor/rusqlite/src/vtab/vtablog.rs
vendored
@@ -1,385 +0,0 @@
|
|||||||
//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
|
|
||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
|
|
||||||
use crate::types::Type;
|
|
||||||
use crate::vtab::{
|
|
||||||
update_module_with_tx, Context, CreateVTab, Filters, IndexInfo, Inserts, TransactionVTab,
|
|
||||||
UpdateVTab, Updates, VTab, VTabConnection, VTabCursor, VTabKind,
|
|
||||||
};
|
|
||||||
use crate::{ffi, ValueRef};
|
|
||||||
use crate::{Connection, Error, Result};
|
|
||||||
|
|
||||||
/// Register the "vtablog" module.
|
|
||||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
|
||||||
let aux: Option<()> = None;
|
|
||||||
conn.create_module(c"vtablog", update_module_with_tx::<VTabLog>(), aux)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An instance of the vtablog virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct VTabLog {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab,
|
|
||||||
/// Associated connection
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
/// Number of rows in the table
|
|
||||||
n_row: i64,
|
|
||||||
/// Instance number for this vtablog table
|
|
||||||
i_inst: usize,
|
|
||||||
/// Number of cursors created
|
|
||||||
n_cursor: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VTabLog {
|
|
||||||
fn connect_create(
|
|
||||||
db: &mut VTabConnection,
|
|
||||||
_: Option<&()>,
|
|
||||||
args: &[&[u8]],
|
|
||||||
is_create: bool,
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
static N_INST: AtomicUsize = AtomicUsize::new(1);
|
|
||||||
let i_inst = N_INST.fetch_add(1, Ordering::SeqCst);
|
|
||||||
println!(
|
|
||||||
"VTabLog::{}(tab={}, args={:?}):",
|
|
||||||
if is_create { "create" } else { "connect" },
|
|
||||||
i_inst,
|
|
||||||
args.iter().map(|b| str::from_utf8(b)).collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
let mut schema = None;
|
|
||||||
let mut n_row = None;
|
|
||||||
|
|
||||||
let args = &args[3..];
|
|
||||||
for c_slice in args {
|
|
||||||
let (param, value) = super::parameter(c_slice)?;
|
|
||||||
match param {
|
|
||||||
"schema" => {
|
|
||||||
if schema.is_some() {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"more than one '{param}' parameter"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
schema = Some(value.to_owned())
|
|
||||||
}
|
|
||||||
"rows" => {
|
|
||||||
if n_row.is_some() {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"more than one '{param}' parameter"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if let Ok(n) = i64::from_str(value) {
|
|
||||||
n_row = Some(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(Error::ModuleError(format!(
|
|
||||||
"unrecognized parameter '{param}'"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if schema.is_none() {
|
|
||||||
return Err(Error::ModuleError("no schema defined".to_owned()));
|
|
||||||
}
|
|
||||||
let vtab = Self {
|
|
||||||
base: ffi::sqlite3_vtab::default(),
|
|
||||||
db: unsafe { db.handle() },
|
|
||||||
n_row: n_row.unwrap_or(10),
|
|
||||||
i_inst,
|
|
||||||
n_cursor: 0,
|
|
||||||
};
|
|
||||||
Ok((schema.unwrap(), vtab))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for VTabLog {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
println!("VTabLog::drop({})", self.i_inst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<'vtab> VTab<'vtab> for VTabLog {
|
|
||||||
type Aux = ();
|
|
||||||
type Cursor = VTabLogCursor<'vtab>;
|
|
||||||
|
|
||||||
fn connect(
|
|
||||||
db: &mut VTabConnection,
|
|
||||||
aux: Option<&Self::Aux>,
|
|
||||||
args: &[&[u8]],
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
Self::connect_create(db, aux, args, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
|
||||||
println!(
|
|
||||||
"VTabLog::best_index({}, num_of_order_by: {}, col_used: {}, distinct: {:?})",
|
|
||||||
self.i_inst,
|
|
||||||
info.num_of_order_by(),
|
|
||||||
info.col_used(),
|
|
||||||
info.distinct()
|
|
||||||
);
|
|
||||||
let mut in_constraint = None;
|
|
||||||
for (i, constraint) in info.constraints().enumerate() {
|
|
||||||
println!(
|
|
||||||
" constraint[{}]: col={}, usable={}, op={:?}, rhs={:?}, in={:?}",
|
|
||||||
i,
|
|
||||||
constraint.column(),
|
|
||||||
constraint.is_usable(),
|
|
||||||
constraint.operator(),
|
|
||||||
info.rhs_value(i),
|
|
||||||
info.is_in_constraint(i),
|
|
||||||
);
|
|
||||||
if info.is_in_constraint(i)? {
|
|
||||||
in_constraint = Some(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.set_estimated_cost(500.);
|
|
||||||
info.set_estimated_rows(500);
|
|
||||||
info.set_idx_str("idx");
|
|
||||||
info.set_idx_cstr(c"idx");
|
|
||||||
if let Some(idx) = in_constraint {
|
|
||||||
info.set_in_constraint(idx, true)?;
|
|
||||||
info.constraint_usage(idx).set_argv_index(1);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&'vtab mut self) -> Result<Self::Cursor> {
|
|
||||||
self.n_cursor += 1;
|
|
||||||
println!(
|
|
||||||
"VTabLog::open(tab={}, cursor={})",
|
|
||||||
self.i_inst, self.n_cursor
|
|
||||||
);
|
|
||||||
Ok(VTabLogCursor {
|
|
||||||
base: ffi::sqlite3_vtab_cursor::default(),
|
|
||||||
i_cursor: self.n_cursor,
|
|
||||||
row_id: 0,
|
|
||||||
phantom: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CreateVTab<'_> for VTabLog {
|
|
||||||
const KIND: VTabKind = VTabKind::Default;
|
|
||||||
|
|
||||||
fn create(
|
|
||||||
db: &mut VTabConnection,
|
|
||||||
aux: Option<&Self::Aux>,
|
|
||||||
args: &[&[u8]],
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
Self::connect_create(db, aux, args, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn destroy(&self) -> Result<()> {
|
|
||||||
println!("VTabLog::destroy({})", self.i_inst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UpdateVTab<'_> for VTabLog {
|
|
||||||
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
|
|
||||||
println!("VTabLog::delete({}, {arg:?})", self.i_inst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(&mut self, args: &Inserts<'_>) -> Result<i64> {
|
|
||||||
println!(
|
|
||||||
"VTabLog::insert({}, on_conflict:{:?}, {:?})",
|
|
||||||
self.i_inst,
|
|
||||||
unsafe { args.on_conflict(self.db) },
|
|
||||||
args.iter().collect::<Vec<ValueRef<'_>>>()
|
|
||||||
);
|
|
||||||
Ok(self.n_row)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, args: &Updates<'_>) -> Result<()> {
|
|
||||||
println!(
|
|
||||||
"VTabLog::update({}, on_conflict:{:?}, {:?})",
|
|
||||||
self.i_inst,
|
|
||||||
unsafe { args.on_conflict(self.db) },
|
|
||||||
args.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, v)| (v, args.no_change(i)))
|
|
||||||
.collect::<Vec<(ValueRef<'_>, bool)>>()
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransactionVTab<'_> for VTabLog {
|
|
||||||
fn begin(&mut self) -> Result<()> {
|
|
||||||
println!("VTabLog::begin({})", self.i_inst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync(&mut self) -> Result<()> {
|
|
||||||
println!("VTabLog::sync({})", self.i_inst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit(&mut self) -> Result<()> {
|
|
||||||
println!("VTabLog::commit({})", self.i_inst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rollback(&mut self) -> Result<()> {
|
|
||||||
println!("VTabLog::rollback({})", self.i_inst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cursor for the Series virtual table
|
|
||||||
#[repr(C)]
|
|
||||||
struct VTabLogCursor<'vtab> {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: ffi::sqlite3_vtab_cursor,
|
|
||||||
/// Cursor number
|
|
||||||
i_cursor: usize,
|
|
||||||
/// The rowid
|
|
||||||
row_id: i64,
|
|
||||||
phantom: PhantomData<&'vtab VTabLog>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VTabLogCursor<'_> {
|
|
||||||
fn vtab(&self) -> &VTabLog {
|
|
||||||
unsafe { &*(self.base.pVtab as *const VTabLog) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for VTabLogCursor<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::drop(tab={}, cursor={})",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl VTabCursor for VTabLogCursor<'_> {
|
|
||||||
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::filter(tab={}, cursor={}, idx_num={idx_num}, idx_str={idx_str:?}, args={})",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor,
|
|
||||||
args.len()
|
|
||||||
);
|
|
||||||
for (i, arg) in args.iter().enumerate() {
|
|
||||||
if arg.data_type() == Type::Null {
|
|
||||||
println!(
|
|
||||||
" in_values[{}]: {:?}",
|
|
||||||
i,
|
|
||||||
args.in_values(i)?.collect::<Vec<ValueRef>>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.row_id = 0;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> Result<()> {
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::next(tab={}, cursor={}): rowid {} -> {}",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor,
|
|
||||||
self.row_id,
|
|
||||||
self.row_id + 1
|
|
||||||
);
|
|
||||||
self.row_id += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eof(&self) -> bool {
|
|
||||||
let eof = self.row_id >= self.vtab().n_row;
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::eof(tab={}, cursor={}): {}",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor,
|
|
||||||
eof,
|
|
||||||
);
|
|
||||||
eof
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
|
||||||
if ctx.no_change() {
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::column(tab={}, cursor={}, i={}): no change",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor,
|
|
||||||
i,
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let value = if i < 26 {
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
"abcdefghijklmnopqrstuvwyz".chars().nth(i as usize).unwrap(),
|
|
||||||
self.row_id
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!("{i}{}", self.row_id)
|
|
||||||
};
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::column(tab={}, cursor={}, i={}): {}",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor,
|
|
||||||
i,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
if i == 0 {
|
|
||||||
println!(" db busy: {:?}", unsafe {
|
|
||||||
ctx.get_connection().map(|c| c.is_busy())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ctx.set_result(&value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rowid(&self) -> Result<i64> {
|
|
||||||
println!(
|
|
||||||
"VTabLogCursor::rowid(tab={}, cursor={}): {}",
|
|
||||||
self.vtab().i_inst,
|
|
||||||
self.i_cursor,
|
|
||||||
self.row_id,
|
|
||||||
);
|
|
||||||
Ok(self.row_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
#[test]
|
|
||||||
fn test_module() -> Result<()> {
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
super::load_module(&db)?;
|
|
||||||
|
|
||||||
db.execute_batch(
|
|
||||||
"CREATE VIRTUAL TABLE temp.log USING vtablog(
|
|
||||||
schema='CREATE TABLE x(a,b,c)',
|
|
||||||
rows=3
|
|
||||||
);",
|
|
||||||
)?;
|
|
||||||
let mut stmt = db.prepare("SELECT * FROM log;")?;
|
|
||||||
let mut rows = stmt.query([])?;
|
|
||||||
while rows.next()?.is_some() {}
|
|
||||||
db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?;
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)",
|
|
||||||
["a", "b", "c"],
|
|
||||||
)?;
|
|
||||||
db.execute(
|
|
||||||
"UPDATE log SET b = ?1, c = ?2 WHERE a = ?3",
|
|
||||||
["bn", "cn", "a1"],
|
|
||||||
)?;
|
|
||||||
db.query_one("SELECT b, c FROM log WHERE a = 'a1'", [], |_| Ok(0))?;
|
|
||||||
db.execute("UPDATE log SET b = '' WHERE a IN (?1, ?2)", ["a1", "a2"])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
vendor/rusqlite/test.csv
vendored
6
vendor/rusqlite/test.csv
vendored
@@ -1,6 +0,0 @@
|
|||||||
"colA","colB","colC"
|
|
||||||
1,2,3
|
|
||||||
a,b,c
|
|
||||||
a,"b",c
|
|
||||||
"a","b","c .. z"
|
|
||||||
"a","b","c,d"
|
|
||||||
|
41
vendor/rusqlite/tests/auto_ext.rs
vendored
41
vendor/rusqlite/tests/auto_ext.rs
vendored
@@ -1,41 +0,0 @@
|
|||||||
#[cfg(all(feature = "bundled", not(feature = "loadable_extension")))]
|
|
||||||
#[test]
|
|
||||||
fn auto_ext() -> rusqlite::Result<()> {
|
|
||||||
use rusqlite::auto_extension::*;
|
|
||||||
use rusqlite::{ffi, Connection, Error, Result};
|
|
||||||
use std::os::raw::{c_char, c_int};
|
|
||||||
|
|
||||||
fn test_ok(_: Connection) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
unsafe extern "C" fn sqlite_test_ok(
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
pz_err_msg: *mut *mut c_char,
|
|
||||||
_: *const ffi::sqlite3_api_routines,
|
|
||||||
) -> c_int {
|
|
||||||
init_auto_extension(db, pz_err_msg, test_ok)
|
|
||||||
}
|
|
||||||
fn test_err(_: Connection) -> Result<()> {
|
|
||||||
Err(Error::SqliteFailure(
|
|
||||||
ffi::Error::new(ffi::SQLITE_CORRUPT),
|
|
||||||
Some("AutoExtErr".to_owned()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
unsafe extern "C" fn sqlite_test_err(
|
|
||||||
db: *mut ffi::sqlite3,
|
|
||||||
pz_err_msg: *mut *mut c_char,
|
|
||||||
_: *const ffi::sqlite3_api_routines,
|
|
||||||
) -> c_int {
|
|
||||||
init_auto_extension(db, pz_err_msg, test_err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//assert!(!cancel_auto_extension(sqlite_test_ok));
|
|
||||||
unsafe { register_auto_extension(sqlite_test_ok)? };
|
|
||||||
Connection::open_in_memory()?;
|
|
||||||
assert!(cancel_auto_extension(sqlite_test_ok));
|
|
||||||
assert!(!cancel_auto_extension(sqlite_test_ok));
|
|
||||||
unsafe { register_auto_extension(sqlite_test_err)? };
|
|
||||||
Connection::open_in_memory().unwrap_err();
|
|
||||||
reset_auto_extension();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
32
vendor/rusqlite/tests/config_log.rs
vendored
32
vendor/rusqlite/tests/config_log.rs
vendored
@@ -1,32 +0,0 @@
|
|||||||
//! This file contains unit tests for `rusqlite::trace::config_log`. This
|
|
||||||
//! function affects SQLite process-wide and so is not safe to run as a normal
|
|
||||||
//! #[test] in the library.
|
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
fn main() {
|
|
||||||
use std::os::raw::c_int;
|
|
||||||
use std::sync::{LazyLock, Mutex};
|
|
||||||
|
|
||||||
static LOGS_RECEIVED: LazyLock<Mutex<Vec<(c_int, String)>>> =
|
|
||||||
LazyLock::new(|| Mutex::new(Vec::new()));
|
|
||||||
|
|
||||||
fn log_handler(err: c_int, message: &str) {
|
|
||||||
let mut logs_received = LOGS_RECEIVED.lock().unwrap();
|
|
||||||
logs_received.push((err, message.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
use rusqlite::trace;
|
|
||||||
|
|
||||||
unsafe { trace::config_log(Some(log_handler)) }.unwrap();
|
|
||||||
trace::log(10, "First message from rusqlite");
|
|
||||||
unsafe { trace::config_log(None) }.unwrap();
|
|
||||||
trace::log(11, "Second message from rusqlite");
|
|
||||||
|
|
||||||
let logs_received = LOGS_RECEIVED.lock().unwrap();
|
|
||||||
assert_eq!(logs_received.len(), 1);
|
|
||||||
assert_eq!(logs_received[0].0, 10);
|
|
||||||
assert_eq!(logs_received[0].1, "First message from rusqlite");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "trace"))]
|
|
||||||
fn main() {}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
//! Ensure we reject connections when SQLite is in single-threaded mode, as it
|
|
||||||
//! would violate safety if multiple Rust threads tried to use connections.
|
|
||||||
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "loadable_extension"))]
|
|
||||||
#[test]
|
|
||||||
fn test_error_when_singlethread_mode() {
|
|
||||||
use rusqlite::ffi;
|
|
||||||
use rusqlite::Connection;
|
|
||||||
// put SQLite into single-threaded mode
|
|
||||||
unsafe {
|
|
||||||
// Note: macOS system SQLite seems to return an error if you attempt to
|
|
||||||
// reconfigure to single-threaded mode.
|
|
||||||
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert_eq!(ffi::sqlite3_initialize(), ffi::SQLITE_OK);
|
|
||||||
}
|
|
||||||
let res = Connection::open_in_memory();
|
|
||||||
res.unwrap_err();
|
|
||||||
}
|
|
||||||
102
vendor/rusqlite/tests/vtab.rs
vendored
102
vendor/rusqlite/tests/vtab.rs
vendored
@@ -1,102 +0,0 @@
|
|||||||
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
|
|
||||||
#[cfg(all(feature = "vtab", target_family = "wasm", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
|
||||||
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
#[test]
|
|
||||||
fn test_dummy_module() -> rusqlite::Result<()> {
|
|
||||||
use rusqlite::vtab::{
|
|
||||||
eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, Filters, IndexInfo,
|
|
||||||
VTab, VTabConnection, VTabCursor,
|
|
||||||
};
|
|
||||||
use rusqlite::{version_number, Connection, Result};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::os::raw::c_int;
|
|
||||||
|
|
||||||
let module = eponymous_only_module::<DummyTab>();
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct DummyTab {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: sqlite3_vtab,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<'vtab> VTab<'vtab> for DummyTab {
|
|
||||||
type Aux = ();
|
|
||||||
type Cursor = DummyTabCursor<'vtab>;
|
|
||||||
|
|
||||||
fn connect(
|
|
||||||
_: &mut VTabConnection,
|
|
||||||
_aux: Option<&()>,
|
|
||||||
_args: &[&[u8]],
|
|
||||||
) -> Result<(String, Self)> {
|
|
||||||
let vtab = Self {
|
|
||||||
base: sqlite3_vtab::default(),
|
|
||||||
};
|
|
||||||
Ok(("CREATE TABLE x(value)".to_owned(), vtab))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
|
||||||
info.set_estimated_cost(1.);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&'vtab mut self) -> Result<DummyTabCursor<'vtab>> {
|
|
||||||
Ok(DummyTabCursor::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct DummyTabCursor<'vtab> {
|
|
||||||
/// Base class. Must be first
|
|
||||||
base: sqlite3_vtab_cursor,
|
|
||||||
/// The rowid
|
|
||||||
row_id: i64,
|
|
||||||
phantom: PhantomData<&'vtab DummyTab>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl VTabCursor for DummyTabCursor<'_> {
|
|
||||||
fn filter(
|
|
||||||
&mut self,
|
|
||||||
_idx_num: c_int,
|
|
||||||
_idx_str: Option<&str>,
|
|
||||||
_args: &Filters<'_>,
|
|
||||||
) -> Result<()> {
|
|
||||||
self.row_id = 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> Result<()> {
|
|
||||||
self.row_id += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eof(&self) -> bool {
|
|
||||||
self.row_id > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column(&self, ctx: &mut Context, _: c_int) -> Result<()> {
|
|
||||||
ctx.set_result(&self.row_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rowid(&self) -> Result<i64> {
|
|
||||||
Ok(self.row_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let db = Connection::open_in_memory()?;
|
|
||||||
|
|
||||||
db.create_module::<DummyTab, _>(c"dummy", module, None)?;
|
|
||||||
|
|
||||||
let version = version_number();
|
|
||||||
if version < 3_009_000 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut s = db.prepare("SELECT * FROM dummy()")?;
|
|
||||||
|
|
||||||
let dummy = s.query_row([], |row| row.get::<_, i32>(0))?;
|
|
||||||
assert_eq!(1, dummy);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user