Compare commits

5 Commits

Author SHA1 Message Date
Zachary Levy
ea19b83ba4 Cleanup 2026-04-21 16:59:11 -07:00
Zachary Levy
7650b90d91 Comment cleanup 2026-04-21 16:09:40 -07:00
Zachary Levy
ba522fa051 QR code improvements 2026-04-21 15:51:17 -07:00
Zachary Levy
a4623a13b5 Basic texture support 2026-04-21 13:46:41 -07:00
Zachary Levy
f85187eff3 Clean up memory management 2026-04-20 22:39:21 -07:00
12 changed files with 1123 additions and 1149 deletions

View File

@@ -1,18 +1,19 @@
package examples package examples
import "core:fmt" import "core:fmt"
import "core:log"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
main :: proc() { main :: proc() {
//----- General setup ---------------------------------- //----- Tracking allocator ----------------------------------
{ {
tracking_temp_allocator := false
// Temp // Temp
track_temp: mem.Tracking_Allocator track_temp: mem.Tracking_Allocator
mem.tracking_allocator_init(&track_temp, context.temp_allocator) if tracking_temp_allocator {
context.temp_allocator = mem.tracking_allocator(&track_temp) mem.tracking_allocator_init(&track_temp, context.temp_allocator)
context.temp_allocator = mem.tracking_allocator(&track_temp)
}
// Default // Default
track: mem.Tracking_Allocator track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator) mem.tracking_allocator_init(&track, context.allocator)
@@ -21,10 +22,18 @@ main :: proc() {
// This could be fine for some global state or it could be a memory leak. // This could be fine for some global state or it could be a memory leak.
defer { defer {
// Temp allocator // Temp allocator
if len(track_temp.bad_free_array) > 0 { if tracking_temp_allocator {
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array)) if len(track_temp.allocation_map) > 0 {
for entry in track_temp.bad_free_array { fmt.eprintf("=== %v allocations not freed - temp allocator: ===\n", len(track_temp.allocation_map))
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location) for _, entry in track_temp.allocation_map {
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
}
}
if len(track_temp.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array))
for entry in track_temp.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
} }
mem.tracking_allocator_destroy(&track_temp) mem.tracking_allocator_destroy(&track_temp)
} }
@@ -43,9 +52,6 @@ main :: proc() {
} }
mem.tracking_allocator_destroy(&track) mem.tracking_allocator_destroy(&track)
} }
// Logger
context.logger = log.create_console_logger()
defer log.destroy_console_logger(context.logger)
} }
args := os.args args := os.args

View File

@@ -1,8 +1,6 @@
package meta package meta
import "core:fmt" import "core:fmt"
import "core:log"
import "core:mem"
import "core:os" import "core:os"
Command :: struct { Command :: struct {
@@ -22,48 +20,6 @@ COMMANDS :: []Command {
} }
main :: proc() { main :: proc() {
//----- General setup ----------------------------------
when ODIN_DEBUG {
// Temp
track_temp: mem.Tracking_Allocator
mem.tracking_allocator_init(&track_temp, context.temp_allocator)
context.temp_allocator = mem.tracking_allocator(&track_temp)
// Default
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
// Log a warning about any memory that was not freed by the end of the program.
// This could be fine for some global state or it could be a memory leak.
defer {
// Temp allocator
if len(track_temp.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array))
for entry in track_temp.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
mem.tracking_allocator_destroy(&track_temp)
}
// Default allocator
if len(track.allocation_map) > 0 {
fmt.eprintf("=== %v allocations not freed - main allocator: ===\n", len(track.allocation_map))
for _, entry in track.allocation_map {
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
}
}
if len(track.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees - main allocator: ===\n", len(track.bad_free_array))
for entry in track.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
}
mem.tracking_allocator_destroy(&track)
}
// Logger
context.logger = log.create_console_logger()
defer log.destroy_console_logger(context.logger)
}
args := os.args[1:] args := os.args[1:]
if len(args) == 0 { if len(args) == 0 {

View File

@@ -1,32 +1,39 @@
package examples package examples
import "core:fmt" import "core:fmt"
import "core:log"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
import qr ".." import qr ".."
main :: proc() { main :: proc() {
//----- General setup ---------------------------------- //----- Tracking allocator ----------------------------------
{ {
tracking_temp_allocator := false
// Temp // Temp
track_temp: mem.Tracking_Allocator track_temp: mem.Tracking_Allocator
mem.tracking_allocator_init(&track_temp, context.temp_allocator) if tracking_temp_allocator {
context.temp_allocator = mem.tracking_allocator(&track_temp) mem.tracking_allocator_init(&track_temp, context.temp_allocator)
context.temp_allocator = mem.tracking_allocator(&track_temp)
}
// Default // Default
track: mem.Tracking_Allocator track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator) mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track) context.allocator = mem.tracking_allocator(&track)
// Log a warning about any memory that was not freed by the end of the program.
// This could be fine for some global state or it could be a memory leak.
defer { defer {
// Temp allocator // Temp allocator
if len(track_temp.bad_free_array) > 0 { if tracking_temp_allocator {
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array)) if len(track_temp.allocation_map) > 0 {
for entry in track_temp.bad_free_array { fmt.eprintf("=== %v allocations not freed - temp allocator: ===\n", len(track_temp.allocation_map))
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location) for _, entry in track_temp.allocation_map {
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
}
}
if len(track_temp.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array))
for entry in track_temp.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
} }
mem.tracking_allocator_destroy(&track_temp) mem.tracking_allocator_destroy(&track_temp)
} }
@@ -45,9 +52,6 @@ main :: proc() {
} }
mem.tracking_allocator_destroy(&track) mem.tracking_allocator_destroy(&track)
} }
// Logger
context.logger = log.create_console_logger()
defer log.destroy_console_logger(context.logger)
} }
args := os.args args := os.args

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,11 +1,8 @@
package examples package examples
import "core:fmt" import "core:fmt"
import "core:log"
import "core:mem"
import "core:os" import "core:os"
import "core:sys/posix" import "core:sys/posix"
import mdb "../../lmdb" import mdb "../../lmdb"
// 0o660 // 0o660
@@ -13,75 +10,34 @@ DB_MODE :: posix.mode_t{.IWGRP, .IRGRP, .IWUSR, .IRUSR}
DB_PATH :: "out/debug/lmdb_example_db" DB_PATH :: "out/debug/lmdb_example_db"
main :: proc() { main :: proc() {
//----- General setup ---------------------------------- environment: ^mdb.Env
{
// Temp
track_temp: mem.Tracking_Allocator
mem.tracking_allocator_init(&track_temp, context.temp_allocator)
context.temp_allocator = mem.tracking_allocator(&track_temp)
// Default // Create environment for lmdb
track: mem.Tracking_Allocator mdb.panic_on_err(mdb.env_create(&environment))
mem.tracking_allocator_init(&track, context.allocator) // Create directory for databases. Won't do anything if it already exists.
context.allocator = mem.tracking_allocator(&track) // 0o774 gives all permissions for owner and group, read for everyone else.
// Log a warning about any memory that was not freed by the end of the program. os.make_directory(DB_PATH, 0o774)
// This could be fine for some global state or it could be a memory leak. // Open the database files (creates them if they don't already exist)
defer { mdb.panic_on_err(mdb.env_open(environment, DB_PATH, 0, DB_MODE))
// Temp allocator
if len(track_temp.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array))
for entry in track_temp.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
mem.tracking_allocator_destroy(&track_temp)
}
// Default allocator
if len(track.allocation_map) > 0 {
fmt.eprintf("=== %v allocations not freed - main allocator: ===\n", len(track.allocation_map))
for _, entry in track.allocation_map {
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
}
}
if len(track.bad_free_array) > 0 {
fmt.eprintf("=== %v incorrect frees - main allocator: ===\n", len(track.bad_free_array))
for entry in track.bad_free_array {
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
}
}
mem.tracking_allocator_destroy(&track)
}
// Logger
context.logger = log.create_console_logger()
defer log.destroy_console_logger(context.logger)
}
environment: ^mdb.Env // Transactions
txn_handle: ^mdb.Txn
db_handle: mdb.Dbi
// Put transaction
key := 7
key_val := mdb.autoval(&key)
put_data := 12
put_data_val := mdb.autoval(&put_data)
mdb.panic_on_err(mdb.txn_begin(environment, nil, 0, &txn_handle))
mdb.panic_on_err(mdb.dbi_open(txn_handle, nil, 0, &db_handle))
mdb.panic_on_err(mdb.put(txn_handle, db_handle, &key_val.raw, &put_data_val.raw, 0))
mdb.panic_on_err(mdb.txn_commit(txn_handle))
// Create environment for lmdb // Get transaction
mdb.panic_on_err(mdb.env_create(&environment)) get_data_val := mdb.nil_autoval(int)
// Create directory for databases. Won't do anything if it already exists. mdb.panic_on_err(mdb.txn_begin(environment, nil, 0, &txn_handle))
os.make_directory(DB_PATH) mdb.panic_on_err(mdb.get(txn_handle, db_handle, &key_val.raw, &get_data_val.raw))
// Open the database files (creates them if they don't already exist) mdb.panic_on_err(mdb.txn_commit(txn_handle))
mdb.panic_on_err(mdb.env_open(environment, DB_PATH, {}, DB_MODE)) data_cpy := mdb.autoval_get_data(&get_data_val)^
fmt.println("Get result:", data_cpy)
// Transactions
txn_handle: ^mdb.Txn
db_handle: mdb.Dbi
// Put transaction
key := 7
key_val := mdb.blittable_val(&key)
put_data := 12
put_data_val := mdb.blittable_val(&put_data)
mdb.panic_on_err(mdb.txn_begin(environment, nil, {}, &txn_handle))
mdb.panic_on_err(mdb.dbi_open(txn_handle, nil, {}, &db_handle))
mdb.panic_on_err(mdb.put(txn_handle, db_handle, &key_val, &put_data_val, {}))
mdb.panic_on_err(mdb.txn_commit(txn_handle))
// Get transaction
data_val: mdb.Val
mdb.panic_on_err(mdb.txn_begin(environment, nil, {}, &txn_handle))
mdb.panic_on_err(mdb.get(txn_handle, db_handle, &key_val, &data_val))
data_cpy := mdb.blittable_copy(&data_val, int)
mdb.panic_on_err(mdb.txn_commit(txn_handle))
fmt.println("Get result:", data_cpy)
} }

350
vendor/lmdb/lmdb.odin vendored
View File

@@ -164,123 +164,24 @@
*/ */
package lmdb package lmdb
foreign import lib "system:lmdb"
import "core:c" import "core:c"
import "core:fmt" import "core:fmt"
import "core:reflect"
import "core:sys/posix" import "core:sys/posix"
// ---------------------------------------------------------------------------------------------------------------------
// ----- Added Odin Helpers ------------------------
// ---------------------------------------------------------------------------------------------------------------------
// Wrap a blittable value's bytes as an LMDB Val.
// T must be a contiguous type with no indirection (no pointers, slices, strings, maps, etc.).
blittable_val :: #force_inline proc(val_ptr: ^$T) -> Val {
fmt.assertf(
reflect.has_no_indirections(type_info_of(T)),
"blitval: type '%v' contains indirection and cannot be stored directly in LMDB",
typeid_of(T),
)
return Val{size_of(T), val_ptr}
}
// Reads a blittable T out of the LMDB memory map by copying it into caller
// storage. The returned T has no lifetime tie to the transaction.
blittable_copy :: #force_inline proc(val: ^Val, $T: typeid) -> T {
fmt.assertf(
reflect.has_no_indirections(type_info_of(T)),
"blitval_copy: type '%v' contains indirection and cannot be read directly from LMDB",
typeid_of(T),
)
return (cast(^T)val.data)^
}
// Zero-copy pointer view into the LMDB memory map as a ^T.
// Useful for large blittable types where you want to read individual fields
// without copying the entire value (e.g. ptr.timestamp, ptr.flags).
// MUST NOT be written through — writes either segfault (default env mode)
// or silently corrupt the database (ENV_WRITEMAP).
// MUST NOT be retained past txn_commit, txn_abort, or any subsequent write
// operation on the same env — the pointer is invalidated.
blittable_view :: #force_inline proc(val: ^Val, $T: typeid) -> ^T {
fmt.assertf(
reflect.has_no_indirections(type_info_of(T)),
"blitval_view: type '%v' contains indirection and cannot be viewed directly from LMDB",
typeid_of(T),
)
return cast(^T)val.data
}
// Wrap a slice of blittable elements as an LMDB Val for use with put/get.
// T must be a contiguous type with no indirection.
// The caller's slice must remain valid (not freed, not resized) for the
// duration of the put call that consumes this Val.
slice_val :: #force_inline proc(s: []$T) -> Val {
fmt.assertf(
reflect.has_no_indirections(type_info_of(T)),
"slice_val: element type '%v' contains indirection and cannot be stored directly in LMDB",
typeid_of(T),
)
return Val{uint(len(s) * size_of(T)), raw_data(s)}
}
// Zero-copy slice view into the LMDB memory map.
// T must match the element type that was originally stored.
// MUST NOT be modified — writes through this slice either segfault (default
// env mode) or silently corrupt the database (ENV_WRITEMAP).
// MUST be copied (e.g. slice.clone) if it needs to outlive the current
// transaction; the view is invalidated by txn_commit, txn_abort, or any
// subsequent write operation on the same env.
slice_view :: #force_inline proc(val: ^Val, $T: typeid) -> []T {
fmt.assertf(
reflect.has_no_indirections(type_info_of(T)),
"slice_view: element type '%v' contains indirection and cannot be read directly from LMDB",
typeid_of(T),
)
return (cast([^]T)val.data)[:val.size / size_of(T)]
}
// Wrap a string's bytes as an LMDB Val for use with put/get.
// The caller's string must remain valid (backing memory not freed) for the
// duration of the put call that consumes this Val.
string_val :: #force_inline proc(s: string) -> Val {
return Val{uint(len(s)), raw_data(s)}
}
// Zero-copy string view into the LMDB memory map.
// MUST NOT be modified — writes through the underlying bytes either segfault
// (default env mode) or silently corrupt the database (ENV_WRITEMAP).
// MUST be copied (e.g. strings.clone) if it needs to outlive the current
// transaction; the view is invalidated by txn_commit, txn_abort, or any
// subsequent write operation on the same env.
string_view :: #force_inline proc(val: ^Val) -> string {
return string((cast([^]u8)val.data)[:val.size])
}
// Panic if there is an error
panic_on_err :: #force_inline proc(error: Error, loc := #caller_location) {
if error != .NONE {
fmt.panicf("LMDB error %v: %s", error, strerror(i32(error)), loc = loc)
}
}
// ---------------------------------------------------------------------------------------------------------------------
// ----- Bindings ------------------------
// ---------------------------------------------------------------------------------------------------------------------
_ :: c _ :: c
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
#panic("TODO: Compile windows .lib for lmdb")
mode_t :: c.int mode_t :: c.int
filehandle_t :: rawptr
} else when ODIN_OS ==
.Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
foreign import lib "system:lmdb"
mode_t :: posix.mode_t
filehandle_t :: c.int
} else { } else {
#panic("levlib/vendor/lmdb: unsupported OS target") mode_t :: posix.mode_t
}
when ODIN_OS == .Windows {
filehandle_t :: rawptr
} else {
filehandle_t :: c.int
} }
Env :: struct {} Env :: struct {}
@@ -288,7 +189,7 @@ Env :: struct {}
Txn :: struct {} Txn :: struct {}
/** @brief A handle for an individual database in the DB environment. */ /** @brief A handle for an individual database in the DB environment. */
Dbi :: c.uint Dbi :: u32
Cursor :: struct {} Cursor :: struct {}
@@ -304,8 +205,33 @@ Cursor :: struct {}
* Other data items can in theory be from 0 to 0xffffffff bytes long. * Other data items can in theory be from 0 to 0xffffffff bytes long.
*/ */
Val :: struct { Val :: struct {
size: uint, /**< size of the data item */ mv_size: uint, /**< size of the data item */
data: rawptr, /**< address of the data item */ mv_data: rawptr, /**< address of the data item */
}
// Automatic `Val` handling for a given type 'T'.
// Will not traverse pointers. If `T` stores pointers, you probably don't want to use this.
Auto_Val :: struct($T: typeid) {
raw: Val,
}
autoval :: #force_inline proc "contextless" (val_ptr: ^$T) -> Auto_Val(T) {
return Auto_Val(T){Val{size_of(T), val_ptr}}
}
nil_autoval :: #force_inline proc "contextless" ($T: typeid) -> Auto_Val(T) {
return Auto_Val(T){Val{size_of(T), nil}}
}
autoval_get_data :: #force_inline proc "contextless" (val: ^Auto_Val($T)) -> ^T {
return cast(^T)val.raw.mv_data
}
// Panic if there is an error
panic_on_err :: #force_inline proc(error: Error) {
if error != .NONE {
fmt.panicf("Irrecoverable LMDB error", strerror(i32(error)))
}
} }
/** @brief A callback function used to compare two keys in a database */ /** @brief A callback function used to compare two keys in a database */
@@ -327,65 +253,85 @@ Cmp_Func :: #type proc "c" (_: ^Val, _: ^Val) -> i32
*/ */
Rel_Func :: #type proc "c" (item: ^Val, oldptr, newptr, relctx: rawptr) Rel_Func :: #type proc "c" (item: ^Val, oldptr, newptr, relctx: rawptr)
/** @defgroup mdb_env Environment Flags /** @defgroup mdb_env Environment Flags
* @{ * @{
*/ */
Env_Flag :: enum u32 { /** mmap at a fixed address (experimental) */
FIXEDMAP = 0, /**< mmap at a fixed address (experimental) */ ENV_FIXEDMAP :: 0x01
NOSUBDIR = 14, /**< no environment directory */ /** no environment directory */
NOSYNC = 16, /**< don't fsync after commit */ ENV_NOSUBDIR :: 0x4000
RDONLY = 17, /**< read only */ /** don't fsync after commit */
NOMETASYNC = 18, /**< don't fsync metapage after commit */ ENV_NOSYNC :: 0x10000
WRITEMAP = 19, /**< use writable mmap */ /** read only */
MAPASYNC = 20, /**< use asynchronous msync when WRITEMAP is used */ ENV_RDONLY :: 0x20000
NOTLS = 21, /**< tie reader locktable slots to Txn objects instead of to threads */ /** don't fsync metapage after commit */
NOLOCK = 22, /**< don't do any locking, caller must manage their own locks */ ENV_NOMETASYNC :: 0x40000
NORDAHEAD = 23, /**< don't do readahead (no effect on Windows) */ /** use writable mmap */
NOMEMINIT = 24, /**< don't initialize malloc'd memory before writing to datafile */ ENV_WRITEMAP :: 0x80000
PREVSNAPSHOT = 25, /**< use the previous snapshot rather than the latest one */ /** use asynchronous msync when #MDB_WRITEMAP is used */
} ENV_MAPASYNC :: 0x100000
Env_Flags :: distinct bit_set[Env_Flag;c.uint] /** tie reader locktable slots to #MDB_txn objects instead of to threads */
ENV_NOTLS :: 0x200000
/** don't do any locking, caller must manage their own locks */
ENV_NOLOCK :: 0x400000
/** don't do readahead (no effect on Windows) */
ENV_NORDAHEAD :: 0x800000
/** don't initialize malloc'd memory before writing to datafile */
ENV_NOMEMINIT :: 0x1000000
/** @} */ /** @} */
/** @defgroup mdb_dbi_open Database Flags /** @defgroup mdb_dbi_open Database Flags
* @{ * @{
*/ */
Db_Flag :: enum u32 { /** use reverse string keys */
REVERSEKEY = 1, /**< use reverse string keys */ DB_REVERSEKEY :: 0x02
DUPSORT = 2, /**< use sorted duplicates */ /** use sorted duplicates */
INTEGERKEY = 3, /**< numeric keys in native byte order */ DB_DUPSORT :: 0x04
DUPFIXED = 4, /**< with DUPSORT, sorted dup items have fixed size */ /** numeric keys in native byte order: either unsigned int or size_t.
INTEGERDUP = 5, /**< with DUPSORT, dups are INTEGERKEY-style integers */ * The keys must all be of the same size. */
REVERSEDUP = 6, /**< with DUPSORT, use reverse string dups */ DB_INTEGERKEY :: 0x08
CREATE = 18, /**< create DB if not already existing */ /** with #MDB_DUPSORT, sorted dup items have fixed size */
} DB_DUPFIXED :: 0x10
Db_Flags :: distinct bit_set[Db_Flag;c.uint] /** with #MDB_DUPSORT, dups are #MDB_INTEGERKEY-style integers */
DB_INTEGERDUP :: 0x20
/** with #MDB_DUPSORT, use reverse string dups */
DB_REVERSEDUP :: 0x40
/** create DB if not already existing */
DB_CREATE :: 0x40000
/** @} */ /** @} */
/** @defgroup mdb_put Write Flags /** @defgroup mdb_put Write Flags
* @{ * @{
*/ */
Write_Flag :: enum u32 { /** For put: Don't write if the key already exists. */
NOOVERWRITE = 4, /**< For put: Don't write if the key already exists */ WRITE_NOOVERWRITE :: 0x10
NODUPDATA = 5, /**< For DUPSORT: don't write if the key and data pair already exist. /** Only for #MDB_DUPSORT<br>
For mdb_cursor_del: remove all duplicate data items. */ * For put: don't write if the key and data pair already exist.<br>
CURRENT = 6, /**< For mdb_cursor_put: overwrite the current key/data pair */ * For mdb_cursor_del: remove all duplicate data items.
RESERVE = 16, /**< For put: Just reserve space for data, don't copy it */ */
APPEND = 17, /**< Data is being appended, don't split full pages */ WRITE_NODUPDATA :: 0x20
APPENDDUP = 18, /**< Duplicate data is being appended, don't split full pages */ /** For mdb_cursor_put: overwrite the current key/data pair */
MULTIPLE = 19, /**< Store multiple data items in one call. Only for DUPFIXED. */ WRITE_CURRENT :: 0x40
} /** For put: Just reserve space for data, don't copy it. Return a
Write_Flags :: distinct bit_set[Write_Flag;c.uint] * pointer to the reserved space.
/** @} */ */
WRITE_RESERVE :: 0x10000
/** Data is being appended, don't split full pages. */
WRITE_APPEND :: 0x20000
/** Duplicate data is being appended, don't split full pages. */
WRITE_APPENDDUP :: 0x40000
/** Store multiple data items in one call. Only for #MDB_DUPFIXED. */
WRITE_MULTIPLE :: 0x80000
/* @} */
/** @defgroup mdb_copy Copy Flags /** @defgroup mdb_copy Copy Flags
* @{ * @{
*/ */
Copy_Flag :: enum u32 { /** Compacting copy: Omit free space from copy, and renumber all
COMPACT = 0, /**< Compacting copy: Omit free space from copy, and renumber all pages sequentially. */ * pages sequentially.
} */
Copy_Flags :: distinct bit_set[Copy_Flag;c.uint] CP_COMPACT :: 0x01
/** @} */ /* @} */
/** @brief Cursor Get operations. /** @brief Cursor Get operations.
* *
@@ -394,24 +340,33 @@ Copy_Flags :: distinct bit_set[Copy_Flag;c.uint]
*/ */
Cursor_Op :: enum c.int { Cursor_Op :: enum c.int {
FIRST, /**< Position at first key/data item */ FIRST, /**< Position at first key/data item */
FIRST_DUP, /**< Position at first data item of current key. Only for DUPSORT */ FIRST_DUP, /**< Position at first data item of current key.
GET_BOTH, /**< Position at key/data pair. Only for DUPSORT */ Only for #MDB_DUPSORT */
GET_BOTH_RANGE, /**< Position at key, nearest data. Only for DUPSORT */ GET_BOTH, /**< Position at key/data pair. Only for #MDB_DUPSORT */
GET_BOTH_RANGE, /**< position at key, nearest data. Only for #MDB_DUPSORT */
GET_CURRENT, /**< Return key/data at current cursor position */ GET_CURRENT, /**< Return key/data at current cursor position */
GET_MULTIPLE, /**< Return up to a page of duplicate data items from current cursor position. Only for DUPFIXED */ GET_MULTIPLE, /**< Return up to a page of duplicate data items
from current cursor position. Move cursor to prepare
for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED */
LAST, /**< Position at last key/data item */ LAST, /**< Position at last key/data item */
LAST_DUP, /**< Position at last data item of current key. Only for DUPSORT */ LAST_DUP, /**< Position at last data item of current key.
Only for #MDB_DUPSORT */
NEXT, /**< Position at next data item */ NEXT, /**< Position at next data item */
NEXT_DUP, /**< Position at next data item of current key. Only for DUPSORT */ NEXT_DUP, /**< Position at next data item of current key.
NEXT_MULTIPLE, /**< Return up to a page of duplicate data items from next cursor position. Only for DUPFIXED */ Only for #MDB_DUPSORT */
NEXT_MULTIPLE, /**< Return up to a page of duplicate data items
from next cursor position. Move cursor to prepare
for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED */
NEXT_NODUP, /**< Position at first data item of next key */ NEXT_NODUP, /**< Position at first data item of next key */
PREV, /**< Position at previous data item */ PREV, /**< Position at previous data item */
PREV_DUP, /**< Position at previous data item of current key. Only for DUPSORT */ PREV_DUP, /**< Position at previous data item of current key.
Only for #MDB_DUPSORT */
PREV_NODUP, /**< Position at last data item of previous key */ PREV_NODUP, /**< Position at last data item of previous key */
SET, /**< Position at specified key */ SET, /**< Position at specified key */
SET_KEY, /**< Position at specified key, return key + data */ SET_KEY, /**< Position at specified key, return key + data */
SET_RANGE, /**< Position at first key greater than or equal to specified key */ SET_RANGE, /**< Position at first key greater than or equal to specified key. */
PREV_MULTIPLE, /**< Position at previous page and return up to a page of duplicate data items. Only for DUPFIXED */ PREV_MULTIPLE, /**< Position at previous page and return up to
a page of duplicate data items. Only for #MDB_DUPFIXED */
} }
Error :: enum c.int { Error :: enum c.int {
@@ -464,28 +419,33 @@ Error :: enum c.int {
BAD_VALSIZE = -30781, BAD_VALSIZE = -30781,
/** The specified DBI was changed unexpectedly */ /** The specified DBI was changed unexpectedly */
BAD_DBI = -30780, BAD_DBI = -30780,
/** Unexpected problem - txn should abort */
PROBLEM = -30779,
} }
/** @brief Statistics for a database in the environment */ /** @brief Statistics for a database in the environment */
Stat :: struct { Stat :: struct {
psize: u32, /**< Size of a database page. This is currently the same for all databases. */ ms_psize: u32,
depth: u32, /**< Depth (height) of the B-tree */ /**< Size of a database page.
branch_pages: uint, /**< Number of internal (non-leaf) pages */ This is currently the same for all databases. */
leaf_pages: uint, /**< Number of leaf pages */ ms_depth: u32,
overflow_pages: uint, /**< Number of overflow pages */ /**< Depth (height) of the B-tree */
entries: uint, /**< Number of data items */ ms_branch_pages: uint,
/**< Number of internal (non-leaf) pages */
ms_leaf_pages: uint,
/**< Number of leaf pages */
ms_overflow_pages: uint,
/**< Number of overflow pages */
ms_entries: uint,
/**< Number of data items */
} }
/** @brief Information about the environment */ /** @brief Information about the environment */
Env_Info :: struct { Env_Info :: struct {
mapaddr: rawptr, /**< Address of map, if fixed */ me_mapaddr: rawptr, /**< Address of map, if fixed */
mapsize: uint, /**< Size of the data memory map */ me_mapsize: uint, /**< Size of the data memory map */
last_pgno: uint, /**< ID of the last used page */ me_last_pgno: uint, /**< ID of the last used page */
last_txnid: uint, /**< ID of the last committed transaction */ me_last_txnid: uint, /**< ID of the last committed transaction */
maxreaders: u32, /**< max reader slots in the environment */ me_maxreaders: u32, /**< max reader slots in the environment */
numreaders: u32, /**< max reader slots used in the environment */ me_numreaders: u32, /**< max reader slots used in the environment */
} }
/** @brief A callback function for most LMDB assert() failures, /** @brief A callback function for most LMDB assert() failures,
@@ -494,7 +454,7 @@ Env_Info :: struct {
* @param[in] env An environment handle returned by #mdb_env_create(). * @param[in] env An environment handle returned by #mdb_env_create().
* @param[in] msg The assertion message, not including newline. * @param[in] msg The assertion message, not including newline.
*/ */
Assert_Func :: #type proc "c" (_: ^Env, _: cstring) Assert_Func :: proc "c" (_: ^Env, _: cstring)
/** @brief A callback function used to print a message from the library. /** @brief A callback function used to print a message from the library.
* *
@@ -502,7 +462,7 @@ Assert_Func :: #type proc "c" (_: ^Env, _: cstring)
* @param[in] ctx An arbitrary context pointer for the callback. * @param[in] ctx An arbitrary context pointer for the callback.
* @return < 0 on failure, >= 0 on success. * @return < 0 on failure, >= 0 on success.
*/ */
Msg_Func :: #type proc "c" (_: cstring, _: rawptr) -> i32 Msg_Func :: proc "c" (_: cstring, _: rawptr) -> i32
@(default_calling_convention = "c", link_prefix = "mdb_") @(default_calling_convention = "c", link_prefix = "mdb_")
foreign lib { foreign lib {
@@ -663,7 +623,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
env_open :: proc(env: ^Env, path: cstring, flags: Env_Flags, mode: mode_t) -> Error --- env_open :: proc(env: ^Env, path: cstring, flags: u32, mode: mode_t) -> Error ---
/** @brief Copy an LMDB environment to the specified path. /** @brief Copy an LMDB environment to the specified path.
* *
@@ -722,7 +682,7 @@ foreign lib {
* @return A non-zero error value on failure and 0 on success. * @return A non-zero error value on failure and 0 on success.
*/ */
@(require_results) @(require_results)
env_copy2 :: proc(env: ^Env, path: cstring, flags: Copy_Flags) -> Error --- env_copy2 :: proc(env: ^Env, path: cstring, flags: u32) -> Error ---
/** @brief Copy an LMDB environment to the specified file descriptor, /** @brief Copy an LMDB environment to the specified file descriptor,
* with options. * with options.
@@ -742,7 +702,7 @@ foreign lib {
* @return A non-zero error value on failure and 0 on success. * @return A non-zero error value on failure and 0 on success.
*/ */
@(require_results) @(require_results)
env_copyfd2 :: proc(env: ^Env, fd: filehandle_t, flags: Copy_Flags) -> Error --- env_copyfd2 :: proc(env: ^Env, fd: filehandle_t, flags: u32) -> Error ---
/** @brief Return statistics about the LMDB environment. /** @brief Return statistics about the LMDB environment.
* *
@@ -807,7 +767,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
env_set_flags :: proc(env: ^Env, flags: Env_Flags, onoff: i32) -> Error --- env_set_flags :: proc(env: ^Env, flags: u32, onoff: i32) -> Error ---
/** @brief Get environment flags. /** @brief Get environment flags.
* *
@@ -820,7 +780,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
env_get_flags :: proc(env: ^Env, flags: ^Env_Flags) -> Error --- env_get_flags :: proc(env: ^Env, flags: ^u32) -> Error ---
/** @brief Return the path that was used in #mdb_env_open(). /** @brief Return the path that was used in #mdb_env_open().
* *
@@ -1013,7 +973,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
txn_begin :: proc(env: ^Env, parent: ^Txn, flags: Env_Flags, txn: ^^Txn) -> Error --- txn_begin :: proc(env: ^Env, parent: ^Txn, flags: u32, txn: ^^Txn) -> Error ---
/** @brief Returns the transaction's #MDB_env /** @brief Returns the transaction's #MDB_env
* *
@@ -1166,7 +1126,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
dbi_open :: proc(txn: ^Txn, name: cstring, flags: Db_Flags, dbi: ^Dbi) -> Error --- dbi_open :: proc(txn: ^Txn, name: cstring, flags: u32, dbi: ^Dbi) -> Error ---
/** @brief Retrieve statistics for a database. /** @brief Retrieve statistics for a database.
* *
@@ -1191,7 +1151,7 @@ foreign lib {
* @return A non-zero error value on failure and 0 on success. * @return A non-zero error value on failure and 0 on success.
*/ */
@(require_results) @(require_results)
dbi_flags :: proc(txn: ^Txn, dbi: Dbi, flags: ^Db_Flags) -> Error --- dbi_flags :: proc(txn: ^Txn, dbi: Dbi, flags: ^u32) -> Error ---
/** @brief Close a database handle. Normally unnecessary. Use with care: /** @brief Close a database handle. Normally unnecessary. Use with care:
* *
@@ -1269,7 +1229,6 @@ foreign lib {
@(require_results) @(require_results)
set_dupsort :: proc(txn: ^Txn, dbi: Dbi, cmp: Cmp_Func) -> Error --- set_dupsort :: proc(txn: ^Txn, dbi: Dbi, cmp: Cmp_Func) -> Error ---
// NOTE: Unimplemented in current LMDB — this function has no effect.
/** @brief Set a relocation function for a #MDB_FIXEDMAP database. /** @brief Set a relocation function for a #MDB_FIXEDMAP database.
* *
* @todo The relocation function is called whenever it is necessary to move the data * @todo The relocation function is called whenever it is necessary to move the data
@@ -1291,7 +1250,6 @@ foreign lib {
@(require_results) @(require_results)
set_relfunc :: proc(txn: ^Txn, dbi: Dbi, rel: Rel_Func) -> Error --- set_relfunc :: proc(txn: ^Txn, dbi: Dbi, rel: Rel_Func) -> Error ---
// NOTE: Unimplemented in current LMDB — this function has no effect.
/** @brief Set a context pointer for a #MDB_FIXEDMAP database's relocation function. /** @brief Set a context pointer for a #MDB_FIXEDMAP database's relocation function.
* *
* See #mdb_set_relfunc and #MDB_rel_func for more details. * See #mdb_set_relfunc and #MDB_rel_func for more details.
@@ -1386,7 +1344,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
put :: proc(txn: ^Txn, dbi: Dbi, key: ^Val, data: ^Val, flags: Write_Flags) -> Error --- put :: proc(txn: ^Txn, dbi: Dbi, key: ^Val, data: ^Val, flags: u32) -> Error ---
/** @brief Delete items from a database. /** @brief Delete items from a database.
* *
@@ -1559,7 +1517,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
cursor_put :: proc(cursor: ^Cursor, key: ^Val, data: ^Val, flags: Write_Flags) -> Error --- cursor_put :: proc(cursor: ^Cursor, key: ^Val, data: ^Val, flags: u32) -> Error ---
/** @brief Delete current key/data pair /** @brief Delete current key/data pair
* *
@@ -1583,7 +1541,7 @@ foreign lib {
* </ul> * </ul>
*/ */
@(require_results) @(require_results)
cursor_del :: proc(cursor: ^Cursor, flags: Write_Flags) -> Error --- cursor_del :: proc(cursor: ^Cursor, flags: u32) -> Error ---
/** @brief Return count of duplicates for current key. /** @brief Return count of duplicates for current key.
* *