Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
version: 0.17.0

- name: Format check
run: zig fmt --check src/ build.zig build.zig.zon
Expand All @@ -55,7 +55,7 @@ jobs:
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
version: 0.17.0

- name: Cache Zig build
uses: actions/cache@v6
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
version: 0.17.0

- name: Cache Zig build
uses: actions/cache@v6
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ zero-dependency SQLite-compatible engine written entirely in Zig.

## Prerequisites

- Zig **0.16.0+** (see [ziglang.org](https://ziglang.org/download/)).
- Zig **0.17.0+** (see [ziglang.org](https://ziglang.org/download/)).
- Windows 10+, Linux, or macOS.
- Git.
- [Bun](https://bun.sh) (only if you work on the documentation website in
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# SQLite.zig

<a href="https://muhammad-fiaz.github.io/sqlite.zig/"><img src="https://img.shields.io/badge/docs-muhammad--fiaz.github.io-blue" alt="Documentation"></a>
<a href="https://ziglang.org/"><img src="https://img.shields.io/badge/Zig-0.16.0-orange.svg?logo=zig" alt="Zig Version"></a>
<a href="https://ziglang.org/"><img src="https://img.shields.io/badge/Zig-0.17.0-orange.svg?logo=zig" alt="Zig Version"></a>
<a href="https://github.com/muhammad-fiaz/sqlite.zig"><img src="https://img.shields.io/github/stars/muhammad-fiaz/sqlite.zig" alt="GitHub stars"></a>
<a href="https://github.com/muhammad-fiaz/sqlite.zig/issues"><img src="https://img.shields.io/github/issues/muhammad-fiaz/sqlite.zig" alt="GitHub issues"></a>
<a href="https://github.com/muhammad-fiaz/sqlite.zig/pulls"><img src="https://img.shields.io/github/issues-pr/muhammad-fiaz/sqlite.zig" alt="GitHub pull requests"></a>
Expand Down Expand Up @@ -121,7 +121,7 @@ Before using `sqlite.zig`, ensure you have the following:

| Requirement | Version | Notes |
|-------------|---------|-------|
| **Zig** | 0.16.0+ | Download from [ziglang.org](https://ziglang.org/download/) |
| **Zig** | 0.17.0+ | Download from [ziglang.org](https://ziglang.org/download/) |
| **Operating System** | Windows 10+, Linux, macOS | Cross-platform database engine |

---
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.name = .sqlite,
.version = "0.0.1",
.fingerprint = 0xb8bb8682c60b0867,
.minimum_zig_version = "0.16.0",
.minimum_zig_version = "0.17.0",
.dependencies = .{},
.paths = .{
"build.zig",
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ description: "Quick start guide for sqlite.zig, a fully native, zero-dependency

## Prerequisites

- **Zig 0.16.0** or later — download from [ziglang.org](https://ziglang.org/download/)
- **Zig 0.17.0** or later — download from [ziglang.org](https://ziglang.org/download/)

## Your First Database

Expand Down
26 changes: 13 additions & 13 deletions src/btree/btree.zig
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub const PageHeader = struct {
const offset = headerOffset(pageNumber);
std.debug.assert(offset + headerSize(self.pageType) <= page.len);
std.debug.assert(self.pageType == .interiorTable or self.pageType == .interiorIndex or self.rightChild == null);
page[offset] = @intFromEnum(self.pageType);
page[offset] = @backingInt(self.pageType);
page[offset + 1] = @truncate(self.firstFreeblock >> 8);
page[offset + 2] = @truncate(self.firstFreeblock);
page[offset + 3] = @truncate(self.cellCount >> 8);
Expand Down Expand Up @@ -345,7 +345,7 @@ test "B-tree keeps keys ordered and replaces values" {
}

test "PageHeader encodes and decodes leaf and interior headers" {
var page = [_]u8{0} ** 4096;
var page = @as([4096]u8, @splat(0));
const leafHeader = PageHeader{
.pageType = .leafTable,
.firstFreeblock = 0,
Expand Down Expand Up @@ -374,15 +374,15 @@ test "PageHeader encodes and decodes leaf and interior headers" {
}

test "formatLeafTablePage and formatInteriorTablePage construct valid pages" {
var page = [_]u8{0} ** 4096;
var page = @as([4096]u8, @splat(0));
const dummyCell = "cell_payload";
const cells = [_][]const u8{ dummyCell, dummyCell };
try formatLeafTablePage(&page, 2, &cells, 4096);
const decoded = try PageHeader.decode(&page, 2);
try std.testing.expectEqual(PageType.leafTable, decoded.pageType);
try std.testing.expectEqual(@as(u16, 2), decoded.cellCount);

var interiorPage = [_]u8{0} ** 4096;
var interiorPage = @as([4096]u8, @splat(0));
const leftKids = [_]u32{ 2, 3 };
const keys = [_]u64{ 10, 20 };
try formatInteriorTablePage(&interiorPage, 4, &leftKids, &keys, 5, 4096);
Expand All @@ -394,7 +394,7 @@ test "formatLeafTablePage and formatInteriorTablePage construct valid pages" {
test "PageHeader rejects unknown flags and short buffers" {
// Error: every non-SQLite flag byte fails closed.
for ([_]u8{ 0x00, 0x01, 0x09, 0xff }) |flag| {
var page = [_]u8{0} ** 512;
var page = @as([512]u8, @splat(0));
page[0] = flag;
try std.testing.expectError(error.InvalidHeader, PageHeader.decode(&page, 2));
}
Expand All @@ -407,7 +407,7 @@ test "PageHeader rejects unknown flags and short buffers" {
shortOne[100] = 0x05; // interior needs 12 bytes, only 1 available
try std.testing.expectError(error.InvalidHeader, PageHeader.decode(&shortOne, 1));
// Normal: page-1 headers decode past the 100-byte reservation.
var pageOne = [_]u8{0} ** 512;
var pageOne = @as([512]u8, @splat(0));
(PageHeader{ .pageType = .leafTable, .cellCount = 1 }).encode(pageOne[0..], 1);
try std.testing.expectEqual(PageType.leafTable, (try PageHeader.decode(pageOne[0..], 1)).pageType);
}
Expand All @@ -425,25 +425,25 @@ test "page formatters fail closed on unfittable and short pages" {
const payloads = [_][]const u8{"p"};
try std.testing.expectError(error.PageOverflow, formatInteriorIndexPage(&small, 2, &kids, &payloads, 3, 4096));
// Error: key/child and payload/child length mismatches.
var page = [_]u8{0} ** 512;
var page = @as([512]u8, @splat(0));
const twoKids = [_]u32{ 2, 3 };
const oneKey = [_]u64{9};
try std.testing.expectError(error.InvalidParam, formatInteriorTablePage(&page, 2, &twoKids, &oneKey, 4, 512));
try std.testing.expectError(error.InvalidParam, formatInteriorIndexPage(&page, 2, &twoKids, &payloads, 4, 512));
// Error: a cell larger than the page cannot fit anywhere.
var big = [_]u8{0} ** 512;
const hugeCell = [_]u8{0xaa} ** 600;
var big = @as([512]u8, @splat(0));
const hugeCell = @as([600]u8, @splat(0xaa));
const hugeCells = [_][]const u8{&hugeCell};
try std.testing.expectError(error.PageOverflow, formatLeafTablePage(&big, 2, &hugeCells, 512));
// Boundary: empty pages format with zero cells and null-free headers.
var blank = [_]u8{0} ** 512;
var blank = @as([512]u8, @splat(0));
try formatLeafTablePage(&blank, 2, &[_][]const u8{}, 512);
try std.testing.expectEqual(@as(u16, 0), (try PageHeader.decode(&blank, 2)).cellCount);
// Normal: index leaf + interior pages round-trip their headers.
var leafIdx = [_]u8{0} ** 512;
var leafIdx = @as([512]u8, @splat(0));
try formatLeafIndexPage(&leafIdx, 2, &cells, 512);
try std.testing.expectEqual(PageType.leafIndex, (try PageHeader.decode(&leafIdx, 2)).pageType);
var interiorIdx = [_]u8{0} ** 512;
var interiorIdx = @as([512]u8, @splat(0));
try formatInteriorIndexPage(&interiorIdx, 2, &kids, &payloads, 9, 512);
try std.testing.expectEqual(@as(u32, 9), (try PageHeader.decode(&interiorIdx, 2)).rightChild.?);
}
Expand Down Expand Up @@ -614,7 +614,7 @@ test "interior formatters frame children with exact cells" {
try payloads.append(alloc, buf);
}
const right: u32 = rand.int(u32);
var page = [_]u8{0} ** 4096;
var page = @as([4096]u8, @splat(0));
if (tableInterior) {
try formatInteriorTablePage(&page, 2, kids.items, keys.items, right, 4096);
} else {
Expand Down
16 changes: 8 additions & 8 deletions src/catalog/type_affinity.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ pub const Affinity = enum {
pub fn fromDeclaration(declaration: []const u8) Affinity {
const trimmed = std.mem.trim(u8, declaration, " \t\r\n");
if (trimmed.len == 0) return .blob;
if (std.ascii.indexOfIgnoreCase(trimmed, "INT")) |_| return .integer;
if (std.ascii.indexOfIgnoreCase(trimmed, "CHAR")) |_| return .text;
if (std.ascii.indexOfIgnoreCase(trimmed, "CLOB")) |_| return .text;
if (std.ascii.indexOfIgnoreCase(trimmed, "TEXT")) |_| return .text;
if (std.ascii.indexOfIgnoreCase(trimmed, "BLOB")) |_| return .blob;
if (std.ascii.indexOfIgnoreCase(trimmed, "REAL")) |_| return .real;
if (std.ascii.indexOfIgnoreCase(trimmed, "FLOA")) |_| return .real;
if (std.ascii.indexOfIgnoreCase(trimmed, "DOUB")) |_| return .real;
if (std.ascii.findIgnoreCase(trimmed, "INT")) |_| return .integer;
if (std.ascii.findIgnoreCase(trimmed, "CHAR")) |_| return .text;
if (std.ascii.findIgnoreCase(trimmed, "CLOB")) |_| return .text;
if (std.ascii.findIgnoreCase(trimmed, "TEXT")) |_| return .text;
if (std.ascii.findIgnoreCase(trimmed, "BLOB")) |_| return .blob;
if (std.ascii.findIgnoreCase(trimmed, "REAL")) |_| return .real;
if (std.ascii.findIgnoreCase(trimmed, "FLOA")) |_| return .real;
if (std.ascii.findIgnoreCase(trimmed, "DOUB")) |_| return .real;
return .numeric;
}

Expand Down
18 changes: 18 additions & 0 deletions src/compat.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const std = @import("std");

pub const Field = struct {
name: [:0]const u8,
type: type,
default_value_ptr: ?*const anyopaque,
is_comptime: bool,
};

// Preserve the old field view while using the Zig 0.17 reflection arrays.
pub fn fields(comptime info: std.builtin.Type.Struct) [info.field_names.len]Field {
@setEvalBranchQuota(100000);
var result: [info.field_names.len]Field = undefined;
for (info.field_names, info.field_types, info.field_attrs, 0..) |name, T, attrs, i| {
result[i] = .{ .name = name, .type = T, .default_value_ptr = attrs.default_value_ptr, .is_comptime = attrs.@"comptime" };
}
return result;
}
13 changes: 7 additions & 6 deletions src/connection/connection.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Engine facade: `Connection` runs SQL against file-backed schemas.
const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const DatabaseFile = @import("../storage/file.zig").DatabaseFile;
const image = @import("../storage/image.zig");
Expand Down Expand Up @@ -691,8 +692,8 @@ pub const Connection = struct {

fn createTypedTable(self: *Connection, comptime TableType: type, options: anytype) !void {
const Row = TableType.rowType;
const rowFields = @typeInfo(Row).@"struct".fields;
const colFields = @typeInfo(@TypeOf(TableType.columns)).@"struct".fields;
const rowFields = reflectionCompat.fields(@typeInfo(Row).@"struct");
const colFields = reflectionCompat.fields(@typeInfo(@TypeOf(TableType.columns)).@"struct");
if (rowFields.len != colFields.len) @compileError("table columns do not match row fields");
var definitions: [colFields.len]ast.ColumnDef = undefined;
inline for (colFields, 0..) |colField, index| {
Expand Down Expand Up @@ -752,8 +753,8 @@ pub const Connection = struct {
const Cols = tableMod.columnsTypeOfValue(T);
const tableOpts = tableMod.tableOptionsField(T).value;
const tname: []const u8 = target.tableName;
const rowFields = @typeInfo(Row).@"struct".fields;
const colFields = @typeInfo(Cols).@"struct".fields;
const rowFields = reflectionCompat.fields(@typeInfo(Row).@"struct");
const colFields = reflectionCompat.fields(@typeInfo(Cols).@"struct");
var definitions: [colFields.len]ast.ColumnDef = undefined;
inline for (colFields, 0..) |colField, index| {
const F = colField.type.fieldType;
Expand Down Expand Up @@ -1117,8 +1118,8 @@ pub const Connection = struct {

pub fn validate(self: @This()) !void {
const t = self.connection.store.findConst(self.tableName) orelse return error.UnknownTable;
const rowFields = @typeInfo(Row).@"struct".fields;
const colFields = @typeInfo(Cols).@"struct".fields;
const rowFields = reflectionCompat.fields(@typeInfo(Row).@"struct");
const colFields = reflectionCompat.fields(@typeInfo(Cols).@"struct");
if (t.columns.len != colFields.len) return error.SchemaMismatch;
if (rowFields.len != colFields.len) return error.SchemaMismatch;
var pkBuf: [16][]const u8 = undefined;
Expand Down
9 changes: 5 additions & 4 deletions src/dsl/keys.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Outputs borrow the inputs; validation only borrows the table.
//! Wrong tables give `UnknownColumn`, bad shapes `InvalidSql`, drift `SchemaMismatch`.

const reflectionCompat = @import("../compat.zig");
const std = @import("std");
const ast = @import("../sql/ast.zig");
const Value = @import("../vm/value.zig").Value;
Expand Down Expand Up @@ -180,7 +181,7 @@ fn referencesWantScope(comptime S: type, comptime T: type) bool {
const info = @typeInfo(T);
if (info == .pointer and info.pointer.size == .one) return referencesWantScope(S, info.pointer.child);
if (info == .@"struct" and info.@"struct".is_tuple) {
inline for (info.@"struct".fields) |field| {
inline for (reflectionCompat.fields(info.@"struct")) |field| {
if (comptime scopeMod.isScopedItem(field.type, S.Row)) return true;
}
}
Expand Down Expand Up @@ -554,7 +555,7 @@ pub fn validateKeys(table: *const schemaMod.Table, expected: *const ExpectedKeys
if (col.unique) actualSingleCount += 1;
}
if (actualSingleCount != expected.uniqueSingleCount) return error.SchemaMismatch;
var matched: [8]bool = .{false} ** 8;
var matched: [8]bool = @splat(false);
var actualGroupCount: usize = 0;
for (table.constraints) |*c| {
if (c.kind != .unique) continue;
Expand All @@ -572,7 +573,7 @@ pub fn validateKeys(table: *const schemaMod.Table, expected: *const ExpectedKeys
if (actualGroupCount != expected.uniqueGroupCount) return error.SchemaMismatch;
}
if (expected.hasFks) {
var matched: [8]bool = .{false} ** 8;
var matched: [8]bool = @splat(false);
var actualCount: usize = 0;
for (table.columns) |*col| {
if (col.foreignTable == null) continue;
Expand Down Expand Up @@ -756,7 +757,7 @@ test "zig type mapping documents the storage contract" {
try std.testing.expectEqualStrings("TEXT", dslTypeName([]const u8));
try std.testing.expectEqualStrings("TEXT", dslTypeName([]u8));
const S = struct { n: i64 = 7, t: []const u8 = "hi", f: f64 = 1.5, o: ?i64 = null, b: bool = true };
inline for (@typeInfo(S).@"struct".fields) |field| {
inline for (reflectionCompat.fields(@typeInfo(S).@"struct")) |field| {
const got = zigDefault(field.type, field.default_value_ptr);
if (std.mem.eql(u8, field.name, "n")) try std.testing.expect(got.?.integer == 7);
if (std.mem.eql(u8, field.name, "t")) try std.testing.expectEqualStrings("hi", got.?.text);
Expand Down
Loading