mongory 0.7.3-x64-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mongory might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +88 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +364 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +488 -0
- data/Rakefile +107 -0
- data/SUBMODULE_INTEGRATION.md +325 -0
- data/docs/advanced_usage.md +40 -0
- data/docs/clang_bridge.md +69 -0
- data/docs/field_names.md +30 -0
- data/docs/migration.md +30 -0
- data/docs/performance.md +61 -0
- data/examples/README.md +41 -0
- data/examples/benchmark-rails.rb +52 -0
- data/examples/benchmark.rb +184 -0
- data/ext/mongory_ext/extconf.rb +91 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +122 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +161 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +79 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +127 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array.c +287 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +19 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config.c +270 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +48 -0
- data/ext/mongory_ext/mongory-core/src/foundations/error.c +38 -0
- data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
- data/ext/mongory_ext/mongory-core/src/foundations/table.c +498 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.c +210 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.h +70 -0
- data/ext/mongory_ext/mongory-core/src/foundations/value.c +500 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +164 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +122 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +100 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +217 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +573 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +147 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +124 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +126 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +314 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +252 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +79 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +23 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +60 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
- data/ext/mongory_ext/mongory_ext.c +683 -0
- data/lib/generators/mongory/install/install_generator.rb +42 -0
- data/lib/generators/mongory/install/templates/initializer.rb.erb +83 -0
- data/lib/generators/mongory/matcher/matcher_generator.rb +56 -0
- data/lib/generators/mongory/matcher/templates/matcher.rb.erb +92 -0
- data/lib/generators/mongory/matcher/templates/matcher_spec.rb.erb +17 -0
- data/lib/mongory/c_query_builder.rb +44 -0
- data/lib/mongory/converters/abstract_converter.rb +111 -0
- data/lib/mongory/converters/condition_converter.rb +64 -0
- data/lib/mongory/converters/converted.rb +81 -0
- data/lib/mongory/converters/data_converter.rb +37 -0
- data/lib/mongory/converters/key_converter.rb +87 -0
- data/lib/mongory/converters/value_converter.rb +52 -0
- data/lib/mongory/converters.rb +8 -0
- data/lib/mongory/matchers/abstract_matcher.rb +219 -0
- data/lib/mongory/matchers/abstract_multi_matcher.rb +124 -0
- data/lib/mongory/matchers/and_matcher.rb +72 -0
- data/lib/mongory/matchers/array_record_matcher.rb +93 -0
- data/lib/mongory/matchers/elem_match_matcher.rb +55 -0
- data/lib/mongory/matchers/eq_matcher.rb +46 -0
- data/lib/mongory/matchers/every_matcher.rb +56 -0
- data/lib/mongory/matchers/exists_matcher.rb +53 -0
- data/lib/mongory/matchers/field_matcher.rb +147 -0
- data/lib/mongory/matchers/gt_matcher.rb +41 -0
- data/lib/mongory/matchers/gte_matcher.rb +41 -0
- data/lib/mongory/matchers/hash_condition_matcher.rb +62 -0
- data/lib/mongory/matchers/in_matcher.rb +68 -0
- data/lib/mongory/matchers/literal_matcher.rb +121 -0
- data/lib/mongory/matchers/lt_matcher.rb +41 -0
- data/lib/mongory/matchers/lte_matcher.rb +41 -0
- data/lib/mongory/matchers/ne_matcher.rb +38 -0
- data/lib/mongory/matchers/nin_matcher.rb +68 -0
- data/lib/mongory/matchers/not_matcher.rb +40 -0
- data/lib/mongory/matchers/or_matcher.rb +68 -0
- data/lib/mongory/matchers/present_matcher.rb +55 -0
- data/lib/mongory/matchers/regex_matcher.rb +80 -0
- data/lib/mongory/matchers/size_matcher.rb +54 -0
- data/lib/mongory/matchers.rb +176 -0
- data/lib/mongory/mongoid.rb +19 -0
- data/lib/mongory/query_builder.rb +257 -0
- data/lib/mongory/query_matcher.rb +93 -0
- data/lib/mongory/query_operator.rb +28 -0
- data/lib/mongory/rails.rb +15 -0
- data/lib/mongory/utils/context.rb +48 -0
- data/lib/mongory/utils/debugger.rb +125 -0
- data/lib/mongory/utils/rails_patch.rb +22 -0
- data/lib/mongory/utils/singleton_builder.rb +31 -0
- data/lib/mongory/utils.rb +76 -0
- data/lib/mongory/version.rb +5 -0
- data/lib/mongory.rb +123 -0
- data/lib/mongory_ext.so +0 -0
- data/mongory.gemspec +62 -0
- data/scripts/build_with_core.sh +292 -0
- data/sig/mongory.rbs +4 -0
- metadata +159 -0
@@ -0,0 +1,498 @@
|
|
1
|
+
/**
|
2
|
+
* @file table.c
|
3
|
+
* @brief Implements the mongory_table hash table.
|
4
|
+
*
|
5
|
+
* This file contains the internal logic for a hash table that maps string keys
|
6
|
+
* to mongory_value pointers. It uses separate chaining for collision resolution,
|
7
|
+
* where each bucket in the hash table is a linked list of nodes. The underlying
|
8
|
+
* storage for buckets is a mongory_array. The table automatically rehashes
|
9
|
+
* when the load factor exceeds a threshold.
|
10
|
+
*/
|
11
|
+
#include "array_private.h" // For mongory_array_private details if needed
|
12
|
+
#include <mongory-core/foundations/array.h>
|
13
|
+
#include <mongory-core/foundations/config.h> // For mongory_string_cpy
|
14
|
+
#include <mongory-core/foundations/table.h>
|
15
|
+
#include <mongory-core/foundations/value.h>
|
16
|
+
#include "utils.h"
|
17
|
+
#include <stdarg.h>
|
18
|
+
#include <string.h> // For strcmp, strlen
|
19
|
+
|
20
|
+
/**
|
21
|
+
* @def MONGORY_TABLE_INIT_SIZE
|
22
|
+
* @brief Initial capacity (number of buckets) for a new hash table.
|
23
|
+
* Should ideally be a prime number.
|
24
|
+
*/
|
25
|
+
#define MONGORY_TABLE_INIT_SIZE 17
|
26
|
+
|
27
|
+
/**
|
28
|
+
* @def MONGORY_TABLE_LOAD_FACTOR
|
29
|
+
* @brief The maximum load factor before the table is rehashed.
|
30
|
+
* Load factor = count / capacity.
|
31
|
+
*/
|
32
|
+
#define MONGORY_TABLE_LOAD_FACTOR 0.75
|
33
|
+
|
34
|
+
/**
|
35
|
+
* @struct mongory_table_node
|
36
|
+
* @brief Represents a node in a hash table bucket's linked list.
|
37
|
+
* Stores a key-value pair and a pointer to the next node in the chain.
|
38
|
+
*/
|
39
|
+
typedef struct mongory_table_node {
|
40
|
+
char *key; /**< The string key for this entry. */
|
41
|
+
mongory_value *value; /**< The mongory_value associated with the key. */
|
42
|
+
struct mongory_table_node *next; /**< Pointer to the next node in the collision chain. */
|
43
|
+
} mongory_table_node;
|
44
|
+
|
45
|
+
/**
|
46
|
+
* @struct mongory_table_internal
|
47
|
+
* @brief Internal representation of the hash table.
|
48
|
+
* Extends the public mongory_table structure with capacity information
|
49
|
+
* and the underlying array used for buckets.
|
50
|
+
*/
|
51
|
+
typedef struct mongory_table_internal {
|
52
|
+
mongory_table base; /**< Public part of the table structure. */
|
53
|
+
size_t capacity; /**< Current number of buckets in the table. */
|
54
|
+
mongory_array *array; /**< Array of mongory_table_node pointers (the buckets). */
|
55
|
+
} mongory_table_internal;
|
56
|
+
|
57
|
+
// ============================================================================
|
58
|
+
// Static Helper Functions
|
59
|
+
//
|
60
|
+
// The following functions are static and provide the core logic for the hash
|
61
|
+
// table operations. They are not part of the public API.
|
62
|
+
// ============================================================================
|
63
|
+
|
64
|
+
/**
|
65
|
+
* @brief Allocates a new mongory_table_node from the table's memory pool.
|
66
|
+
* @param self Pointer to the mongory_table (used to access its memory pool).
|
67
|
+
* @return mongory_table_node* Pointer to the new node, or NULL on failure.
|
68
|
+
*/
|
69
|
+
static inline mongory_table_node *mongory_table_node_new(mongory_table *self) {
|
70
|
+
return MG_ALLOC_PTR(self->pool, mongory_table_node);
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* @brief Finds the next prime number greater than or equal to n.
|
75
|
+
*
|
76
|
+
* Using a prime number for the capacity of a hash table helps to distribute
|
77
|
+
* keys more uniformly, reducing collisions.
|
78
|
+
*
|
79
|
+
* @param n The number to start searching from.
|
80
|
+
* @return The next prime number.
|
81
|
+
*/
|
82
|
+
static inline size_t next_prime(size_t n) {
|
83
|
+
if (n <= 2) {
|
84
|
+
return 2;
|
85
|
+
}
|
86
|
+
if (n % 2 == 0) // Ensure n is odd to start.
|
87
|
+
n++;
|
88
|
+
|
89
|
+
while (1) {
|
90
|
+
bool is_prime = true;
|
91
|
+
// Check divisibility up to sqrt(n).
|
92
|
+
for (size_t i = 3; i * i <= n; i += 2) {
|
93
|
+
if (n % i == 0) {
|
94
|
+
is_prime = false;
|
95
|
+
break;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
if (is_prime) {
|
99
|
+
return n;
|
100
|
+
}
|
101
|
+
n += 2; // Check next odd number.
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
/**
|
106
|
+
* @brief Computes a hash value for a null-terminated string (djb2 algorithm).
|
107
|
+
* @param str The string to hash.
|
108
|
+
* @return The hash value.
|
109
|
+
*/
|
110
|
+
static inline size_t hash_string(const char *str) {
|
111
|
+
size_t hash = 5381; // Initial hash value.
|
112
|
+
int c;
|
113
|
+
while ((c = *str++)) { // Iterate through characters of the string.
|
114
|
+
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
|
115
|
+
}
|
116
|
+
return hash;
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* @brief Walks a linked list of mongory_table_node, applying a callback to each.
|
121
|
+
* @param head The head of the linked list.
|
122
|
+
* @param acc Accumulator/context for the callback.
|
123
|
+
* @param callback Function to call for each node. Stops if callback returns
|
124
|
+
* false.
|
125
|
+
* @return true if iteration completed, false if callback stopped it.
|
126
|
+
*/
|
127
|
+
static inline bool mongory_table_node_walk(mongory_table_node *head, void *acc,
|
128
|
+
bool (*callback)(mongory_table_node *node, void *acc)) {
|
129
|
+
for (mongory_table_node *node = head; node;) {
|
130
|
+
mongory_table_node *next = node->next; // Save next before callback modifies node
|
131
|
+
if (!callback(node, acc)) {
|
132
|
+
return false; // Callback requested stop.
|
133
|
+
}
|
134
|
+
node = next;
|
135
|
+
}
|
136
|
+
return true;
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* @brief Callback used during rehashing to re-insert a node into the new table
|
141
|
+
* structure.
|
142
|
+
* Calculates the new bucket index for the node and prepends it to that
|
143
|
+
* bucket's list.
|
144
|
+
* @param node The node to rehash.
|
145
|
+
* @param acc Pointer to the mongory_table_internal structure (cast from void*).
|
146
|
+
* @return Always true to continue walking the old bucket list.
|
147
|
+
*/
|
148
|
+
static inline bool mongory_table_rehash_on_node(mongory_table_node *node, void *acc) {
|
149
|
+
mongory_table_internal *internal = (mongory_table_internal *)acc;
|
150
|
+
mongory_array *new_bucket_array = internal->array;
|
151
|
+
// Calculate index in the new array based on new capacity.
|
152
|
+
size_t new_index = hash_string(node->key) % internal->capacity;
|
153
|
+
|
154
|
+
// Get current head of the new bucket's list.
|
155
|
+
mongory_table_node *current_bucket_head = (mongory_table_node *)new_bucket_array->get(new_bucket_array, new_index);
|
156
|
+
// Prepend the node to this list.
|
157
|
+
node->next = current_bucket_head;
|
158
|
+
new_bucket_array->set(new_bucket_array, new_index, (mongory_value *)node);
|
159
|
+
return true;
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* @brief Rehashes the table when the load factor is exceeded.
|
164
|
+
* Creates a new underlying array with a larger, prime capacity, and
|
165
|
+
* re-inserts all existing nodes into this new array.
|
166
|
+
* @param self Pointer to the mongory_table.
|
167
|
+
* @return true if rehashing was successful, false otherwise.
|
168
|
+
*/
|
169
|
+
static inline bool mongory_table_rehash(mongory_table *self) {
|
170
|
+
mongory_table_internal *internal = (mongory_table_internal *)self;
|
171
|
+
mongory_array_private *old_array_private_view = (mongory_array_private *)internal->array;
|
172
|
+
|
173
|
+
mongory_value **original_items_ptr = old_array_private_view->items;
|
174
|
+
size_t original_capacity = internal->capacity;
|
175
|
+
size_t new_capacity = next_prime(original_capacity * 2);
|
176
|
+
|
177
|
+
// Reset the count of the existing array before resizing, as resize
|
178
|
+
// might copy elements if count is not zero. We are managing elements manually.
|
179
|
+
internal->array->count = 0;
|
180
|
+
|
181
|
+
if (!mongory_array_resize(internal->array, new_capacity)) {
|
182
|
+
// Error: Rehashing failed because the underlying array could not be resized.
|
183
|
+
// The table remains functional but may have a suboptimal load factor.
|
184
|
+
self->pool->error = &MONGORY_ALLOC_ERROR;
|
185
|
+
internal->array->count = self->count; // Try to restore roughly
|
186
|
+
return false;
|
187
|
+
}
|
188
|
+
// After resize, internal->array (and old_array_private_view) points to the
|
189
|
+
// new items array. The old items array (original_items_ptr) is now detached
|
190
|
+
// but its contents (the nodes) are still valid.
|
191
|
+
|
192
|
+
internal->capacity = new_capacity; // Update table's capacity.
|
193
|
+
|
194
|
+
// Iterate through all buckets of the old array structure.
|
195
|
+
for (size_t i = 0; i < original_capacity; i++) {
|
196
|
+
// Walk the linked list in each old bucket and rehash each node.
|
197
|
+
mongory_table_node_walk((mongory_table_node *)original_items_ptr[i], self, mongory_table_rehash_on_node);
|
198
|
+
}
|
199
|
+
// The memory for original_items_ptr itself (the array of pointers) is now
|
200
|
+
// stale / managed by the memory pool if mongory_array_resize reallocated.
|
201
|
+
// The mongory_table_nodes it pointed to have been relinked into the new array.
|
202
|
+
return true;
|
203
|
+
}
|
204
|
+
|
205
|
+
/**
|
206
|
+
* @struct mongory_table_kv_context
|
207
|
+
* @brief Context structure used for get and set operations within a bucket
|
208
|
+
* list.
|
209
|
+
*/
|
210
|
+
typedef struct mongory_table_kv_context {
|
211
|
+
char *key; /**< The key being searched for or set. */
|
212
|
+
mongory_value *value; /**< Stores the found value (for get) or the value to set. */
|
213
|
+
} mongory_table_kv_context;
|
214
|
+
|
215
|
+
/**
|
216
|
+
* @brief Callback for mongory_table_get to find a key in a bucket's list.
|
217
|
+
* If the node's key matches ctx->key, sets ctx->value and returns false to stop.
|
218
|
+
* @param node Current node in the list.
|
219
|
+
* @param acc Pointer to mongory_table_kv_context.
|
220
|
+
* @return true to continue search, false if key found.
|
221
|
+
*/
|
222
|
+
static inline bool mongory_table_get_on_node(mongory_table_node *node, void *acc) {
|
223
|
+
mongory_table_kv_context *ctx = (mongory_table_kv_context *)acc;
|
224
|
+
if (strcmp(node->key, ctx->key) == 0) {
|
225
|
+
ctx->value = node->value; // Key found, store value.
|
226
|
+
return false; // Stop search.
|
227
|
+
}
|
228
|
+
return true; // Continue search.
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* @brief Retrieves a value associated with a key. Implements `table->get`.
|
233
|
+
* @param self Pointer to the mongory_table.
|
234
|
+
* @param key The key to search for.
|
235
|
+
* @return Pointer to the mongory_value, or NULL if not found.
|
236
|
+
*/
|
237
|
+
mongory_value *mongory_table_get(mongory_table *self, char *key) {
|
238
|
+
mongory_table_internal *internal = (mongory_table_internal *)self;
|
239
|
+
mongory_array *bucket_array = internal->array;
|
240
|
+
size_t index = hash_string(key) % internal->capacity;
|
241
|
+
|
242
|
+
mongory_table_node *bucket_head = (mongory_table_node *)bucket_array->get(bucket_array, index);
|
243
|
+
|
244
|
+
mongory_table_kv_context ctx = {key, NULL};
|
245
|
+
mongory_table_node_walk(bucket_head, &ctx, mongory_table_get_on_node);
|
246
|
+
return ctx.value; // NULL if not found, otherwise the value.
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* @brief Callback for mongory_table_set to update a value if key exists in a
|
251
|
+
* bucket's list.
|
252
|
+
* If node's key matches ctx->key, updates node's value and returns false to
|
253
|
+
* stop.
|
254
|
+
* @param node Current node in the list.
|
255
|
+
* @param acc Pointer to mongory_table_kv_context.
|
256
|
+
* @return true to continue search (if key not matched), false if key updated.
|
257
|
+
*/
|
258
|
+
static inline bool mongory_table_set_on_node(mongory_table_node *node, void *acc) {
|
259
|
+
mongory_table_kv_context *ctx = (mongory_table_kv_context *)acc;
|
260
|
+
if (strcmp(node->key, ctx->key) == 0) {
|
261
|
+
node->value = ctx->value; // Key found, update value.
|
262
|
+
return false; // Stop search, indicates update happened.
|
263
|
+
}
|
264
|
+
return true; // Continue search.
|
265
|
+
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* @brief Sets (adds or updates) a key-value pair in the table. Implements
|
269
|
+
* `table->set`.
|
270
|
+
* If key exists, its value is updated. Otherwise, a new node is created and
|
271
|
+
* added. Triggers rehashing if load factor is exceeded after adding.
|
272
|
+
* @param self Pointer to the mongory_table.
|
273
|
+
* @param key The key to set. A copy of this key will be made.
|
274
|
+
* @param value The value to associate with the key.
|
275
|
+
* @return true if successful, false on failure (e.g., memory allocation).
|
276
|
+
*/
|
277
|
+
bool mongory_table_set(mongory_table *self, char *key, mongory_value *value) {
|
278
|
+
mongory_table_internal *internal = (mongory_table_internal *)self;
|
279
|
+
mongory_array *bucket_array = internal->array;
|
280
|
+
size_t index = hash_string(key) % internal->capacity;
|
281
|
+
|
282
|
+
mongory_table_node *bucket_head = (mongory_table_node *)bucket_array->get(bucket_array, index);
|
283
|
+
|
284
|
+
mongory_table_kv_context ctx = {key, value};
|
285
|
+
// Try to find and update existing key. If mongory_table_node_walk returns
|
286
|
+
// false, it means set_on_node found the key and updated it.
|
287
|
+
if (!mongory_table_node_walk(bucket_head, &ctx, mongory_table_set_on_node)) {
|
288
|
+
return true; // Update successful.
|
289
|
+
}
|
290
|
+
|
291
|
+
// Key not found, create a new node and prepend it to the bucket list.
|
292
|
+
mongory_table_node *new_node = mongory_table_node_new(self);
|
293
|
+
if (!new_node) {
|
294
|
+
self->pool->error = &MONGORY_ALLOC_ERROR;
|
295
|
+
return false; // Node allocation failed.
|
296
|
+
}
|
297
|
+
|
298
|
+
char *key_copy = mongory_string_cpy(self->pool, key);
|
299
|
+
if (!key_copy) {
|
300
|
+
self->pool->error = &MONGORY_ALLOC_ERROR;
|
301
|
+
return false; // Key copy failed.
|
302
|
+
}
|
303
|
+
|
304
|
+
new_node->key = key_copy;
|
305
|
+
new_node->value = value;
|
306
|
+
new_node->next = bucket_head; // Prepend to list.
|
307
|
+
bucket_array->set(bucket_array, index, (mongory_value *)new_node);
|
308
|
+
|
309
|
+
self->count++;
|
310
|
+
// Check load factor and rehash if necessary.
|
311
|
+
if (self->count > internal->capacity * MONGORY_TABLE_LOAD_FACTOR) {
|
312
|
+
if (!mongory_table_rehash(self)) {
|
313
|
+
// Rehashing failed. The table will still work, but its performance
|
314
|
+
// may be degraded due to a higher-than-optimal load factor.
|
315
|
+
self->pool->error = &MONGORY_ALLOC_ERROR;
|
316
|
+
}
|
317
|
+
}
|
318
|
+
return true;
|
319
|
+
}
|
320
|
+
|
321
|
+
/**
|
322
|
+
* @brief Deletes a key-value pair from the table. Implements `table->del`.
|
323
|
+
* @param self Pointer to the mongory_table.
|
324
|
+
* @param key The key to delete.
|
325
|
+
* @return true if key was found and deleted, false otherwise.
|
326
|
+
*/
|
327
|
+
bool mongory_table_del(mongory_table *self, char *key) {
|
328
|
+
mongory_table_internal *internal = (mongory_table_internal *)self;
|
329
|
+
mongory_array *bucket_array = internal->array;
|
330
|
+
size_t index = hash_string(key) % internal->capacity;
|
331
|
+
|
332
|
+
mongory_table_node *node = (mongory_table_node *)bucket_array->get(bucket_array, index);
|
333
|
+
mongory_table_node *prev = NULL;
|
334
|
+
|
335
|
+
while (node) {
|
336
|
+
if (strcmp(node->key, key) == 0) {
|
337
|
+
if (prev) {
|
338
|
+
prev->next = node->next; // Unlink from middle/end of list.
|
339
|
+
} else {
|
340
|
+
// Node is the head of the list for this bucket.
|
341
|
+
bucket_array->set(bucket_array, index, (mongory_value *)node->next);
|
342
|
+
}
|
343
|
+
// The memory for the node and its key is not freed here.
|
344
|
+
// It was allocated from the memory pool and will be reclaimed all at
|
345
|
+
// once when the pool is destroyed. This is a core design principle
|
346
|
+
// of the library's memory management.
|
347
|
+
self->count--;
|
348
|
+
return true; // Deletion successful.
|
349
|
+
}
|
350
|
+
prev = node;
|
351
|
+
node = node->next;
|
352
|
+
}
|
353
|
+
return false; // Key not found.
|
354
|
+
}
|
355
|
+
|
356
|
+
/**
|
357
|
+
* @struct mongory_table_each_pair_context
|
358
|
+
* @brief Context for iterating over table key-value pairs.
|
359
|
+
*/
|
360
|
+
typedef struct mongory_table_each_pair_context {
|
361
|
+
void *acc; /**< User-provided accumulator. */
|
362
|
+
mongory_table_each_pair_callback_func callback; /**< User callback function. */
|
363
|
+
} mongory_table_each_pair_context;
|
364
|
+
|
365
|
+
/**
|
366
|
+
* @brief Callback for mongory_table_each_pair, applied to each node in a
|
367
|
+
* bucket list. Invokes the user's callback.
|
368
|
+
* @param node Current table node.
|
369
|
+
* @param acc Pointer to mongory_table_each_pair_context.
|
370
|
+
* @return Result of the user's callback.
|
371
|
+
*/
|
372
|
+
static inline bool mongory_table_each_pair_on_node(mongory_table_node *node, void *acc) {
|
373
|
+
mongory_table_each_pair_context *ctx = (mongory_table_each_pair_context *)acc;
|
374
|
+
return ctx->callback(node->key, node->value, ctx->acc);
|
375
|
+
}
|
376
|
+
|
377
|
+
/**
|
378
|
+
* @brief Callback for iterating through the table's underlying array of buckets.
|
379
|
+
* For each non-empty bucket (which is a mongory_table_node* cast to
|
380
|
+
* mongory_value*), it walks that bucket's linked list.
|
381
|
+
* @param value The head of a bucket's linked list (cast to mongory_value*).
|
382
|
+
* @param acc Pointer to mongory_table_each_pair_context.
|
383
|
+
* @return Result of mongory_table_node_walk on the bucket list.
|
384
|
+
*/
|
385
|
+
static inline bool mongory_table_each_pair_on_root(mongory_value *value, void *acc) {
|
386
|
+
mongory_table_node *node_head = (mongory_table_node *)value;
|
387
|
+
// If bucket is empty (value is NULL from array->get), this will do nothing.
|
388
|
+
return mongory_table_node_walk(node_head, acc, mongory_table_each_pair_on_node);
|
389
|
+
}
|
390
|
+
|
391
|
+
/**
|
392
|
+
* @brief Iterates over all key-value pairs in the table. Implements
|
393
|
+
* `table->each`.
|
394
|
+
* @param self Pointer to the mongory_table.
|
395
|
+
* @param acc Accumulator/context for the user callback.
|
396
|
+
* @param callback User function to call for each pair.
|
397
|
+
* @return true if iteration completed, false if callback stopped it.
|
398
|
+
*/
|
399
|
+
bool mongory_table_each_pair(mongory_table *self, void *acc, mongory_table_each_pair_callback_func callback) {
|
400
|
+
mongory_table_internal *internal = (mongory_table_internal *)self;
|
401
|
+
mongory_table_each_pair_context each_ctx = {acc, callback};
|
402
|
+
// Iterate over the array of buckets. Each element in this array is the head
|
403
|
+
// of a linked list of table nodes (or NULL if bucket is empty).
|
404
|
+
return internal->array->each(internal->array, &each_ctx, mongory_table_each_pair_on_root);
|
405
|
+
}
|
406
|
+
|
407
|
+
/**
|
408
|
+
* @brief Creates and initializes a new mongory_table.
|
409
|
+
* Allocates the table structure, its internal array for buckets, and sets up
|
410
|
+
* function pointers.
|
411
|
+
* @param pool The memory pool to use for all allocations.
|
412
|
+
* @return Pointer to the new mongory_table, or NULL on failure.
|
413
|
+
*/
|
414
|
+
mongory_table *mongory_table_new(mongory_memory_pool *pool) {
|
415
|
+
if (!pool)
|
416
|
+
return NULL; // Must have a valid pool.
|
417
|
+
|
418
|
+
size_t init_capacity = MONGORY_TABLE_INIT_SIZE;
|
419
|
+
mongory_array *bucket_array = mongory_array_new(pool);
|
420
|
+
|
421
|
+
// Initialize the bucket array. We resize it to the initial capacity and
|
422
|
+
// then set the last element to NULL. The `mongory_array_set` function
|
423
|
+
// will automatically fill all intermediate slots with NULL if the index is
|
424
|
+
// out of bounds, which perfectly initializes our bucket array.
|
425
|
+
bool array_init_success = bucket_array && mongory_array_resize(bucket_array, init_capacity) &&
|
426
|
+
bucket_array->set(bucket_array, init_capacity - 1, NULL);
|
427
|
+
|
428
|
+
if (!array_init_success) {
|
429
|
+
// If array initialization fails, we cannot proceed.
|
430
|
+
// The memory for `bucket_array` itself (if allocated) will be handled
|
431
|
+
// by the memory pool when it's eventually freed.
|
432
|
+
pool->error = &MONGORY_ALLOC_ERROR;
|
433
|
+
return NULL;
|
434
|
+
}
|
435
|
+
// After initialization, bucket_array->count will equal init_capacity.
|
436
|
+
// This is correct for the array's state, but the table's logical count is 0.
|
437
|
+
// We leave array->count as is, since the table's `each` function iterates
|
438
|
+
// over the array's full capacity.
|
439
|
+
bucket_array->count = init_capacity;
|
440
|
+
|
441
|
+
mongory_table_internal *internal = MG_ALLOC_PTR(pool, mongory_table_internal);
|
442
|
+
if (!internal) {
|
443
|
+
pool->error = &MONGORY_ALLOC_ERROR;
|
444
|
+
return NULL;
|
445
|
+
}
|
446
|
+
|
447
|
+
// Initialize public part (base)
|
448
|
+
internal->base.pool = pool;
|
449
|
+
internal->base.count = 0; // Table is initially empty.
|
450
|
+
internal->base.each = mongory_table_each_pair;
|
451
|
+
internal->base.get = mongory_table_get;
|
452
|
+
internal->base.set = mongory_table_set;
|
453
|
+
internal->base.del = mongory_table_del;
|
454
|
+
|
455
|
+
// Initialize private part
|
456
|
+
internal->array = bucket_array;
|
457
|
+
internal->capacity = init_capacity;
|
458
|
+
|
459
|
+
return &internal->base; // Return pointer to the public structure.
|
460
|
+
}
|
461
|
+
|
462
|
+
mongory_table *mongory_table_nested_wrap(mongory_memory_pool *pool, int argc, ...) {
|
463
|
+
mongory_table *table = mongory_table_new(pool);
|
464
|
+
if (!table) {
|
465
|
+
return NULL;
|
466
|
+
}
|
467
|
+
va_list args;
|
468
|
+
va_start(args, argc);
|
469
|
+
for (int i = 0; i < argc; i++) {
|
470
|
+
char *key = va_arg(args, char *);
|
471
|
+
MONGORY_VALIDATE_PTR(pool, key);
|
472
|
+
mongory_value *value = va_arg(args, mongory_value *);
|
473
|
+
MONGORY_VALIDATE_PTR(pool, value);
|
474
|
+
mongory_table_set(table, key, value);
|
475
|
+
}
|
476
|
+
va_end(args);
|
477
|
+
if (pool->error != NULL) {
|
478
|
+
return NULL;
|
479
|
+
}
|
480
|
+
return table;
|
481
|
+
}
|
482
|
+
|
483
|
+
static inline bool mongory_table_merge_cb(char *key, mongory_value *value, void *acc) {
|
484
|
+
mongory_table *table = (mongory_table *)acc;
|
485
|
+
table->set(table, key, value);
|
486
|
+
return true;
|
487
|
+
}
|
488
|
+
|
489
|
+
mongory_table *mongory_table_merge(mongory_table *table, mongory_table *other) {
|
490
|
+
mongory_memory_pool *pool = other->pool;
|
491
|
+
MONGORY_VALIDATE_PTR(pool, other->each);
|
492
|
+
MONGORY_VALIDATE_PTR(pool, table->set);
|
493
|
+
if (pool->error != NULL) {
|
494
|
+
return NULL;
|
495
|
+
}
|
496
|
+
other->each(other, table, mongory_table_merge_cb);
|
497
|
+
return table;
|
498
|
+
}
|
@@ -0,0 +1,210 @@
|
|
1
|
+
#ifndef MONGORY_UTILS_C
|
2
|
+
#define MONGORY_UTILS_C
|
3
|
+
|
4
|
+
#include "utils.h"
|
5
|
+
#include <errno.h>
|
6
|
+
#include <limits.h>
|
7
|
+
#include <stdlib.h>
|
8
|
+
#include <stdbool.h>
|
9
|
+
#include <stdarg.h>
|
10
|
+
#include <string.h>
|
11
|
+
#include <stdio.h>
|
12
|
+
#include <math.h>
|
13
|
+
|
14
|
+
/**
|
15
|
+
* @brief Attempts to parse a string `key` into an integer `out`.
|
16
|
+
*
|
17
|
+
* Uses `strtol` for conversion and checks for common parsing errors:
|
18
|
+
* - Input string is NULL or empty.
|
19
|
+
* - The string contains non-numeric characters after the number.
|
20
|
+
* - The parsed number is out of the range of `int` (`INT_MIN`, `INT_MAX`).
|
21
|
+
*
|
22
|
+
* @param key The null-terminated string to parse.
|
23
|
+
* @param out Pointer to an integer where the result is stored.
|
24
|
+
* @return `true` if parsing is successful and the value fits in an `int`.
|
25
|
+
* `false` otherwise. `errno` may be set by `strtol`.
|
26
|
+
*/
|
27
|
+
bool mongory_try_parse_int(const char *key, int *out) {
|
28
|
+
if (key == NULL || *key == '\0') {
|
29
|
+
return false; // Invalid input string.
|
30
|
+
}
|
31
|
+
if (out == NULL) {
|
32
|
+
return false; // Output pointer must be valid.
|
33
|
+
}
|
34
|
+
|
35
|
+
char *endptr = NULL;
|
36
|
+
errno = 0; // Clear errno before calling strtol.
|
37
|
+
long val = strtol(key, &endptr, 10); // Base 10 conversion.
|
38
|
+
|
39
|
+
// Check for parsing errors reported by strtol.
|
40
|
+
if (endptr == key) {
|
41
|
+
return false; // No digits were found.
|
42
|
+
}
|
43
|
+
if (*endptr != '\0') {
|
44
|
+
return false; // Additional characters after the number.
|
45
|
+
}
|
46
|
+
if (errno == ERANGE || val < INT_MIN || val > INT_MAX) {
|
47
|
+
// Value out of range for int. ERANGE is set by strtol for overflow/underflow.
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
|
51
|
+
*out = (int)val; // Successfully parsed and within int range.
|
52
|
+
return true;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* @brief Creates a copy of a string using the specified memory pool.
|
57
|
+
* @param pool The memory pool to use for allocation.
|
58
|
+
* @param str The null-terminated string to copy.
|
59
|
+
* @return A pointer to the newly allocated copy, or NULL if str is NULL or
|
60
|
+
* allocation fails.
|
61
|
+
*/
|
62
|
+
char *mongory_string_cpy(mongory_memory_pool *pool, char *str) {
|
63
|
+
if (str == NULL) {
|
64
|
+
return NULL;
|
65
|
+
}
|
66
|
+
|
67
|
+
size_t len = strlen(str);
|
68
|
+
char *new_str = (char *)MG_ALLOC(pool, len + 1); // +1 for null terminator.
|
69
|
+
if (new_str == NULL) {
|
70
|
+
pool->error = &MONGORY_ALLOC_ERROR;
|
71
|
+
return NULL;
|
72
|
+
}
|
73
|
+
|
74
|
+
strcpy(new_str, str);
|
75
|
+
return new_str;
|
76
|
+
}
|
77
|
+
|
78
|
+
char *mongory_string_cpyf(mongory_memory_pool *pool, char *format, ...) {
|
79
|
+
va_list args;
|
80
|
+
va_start(args, format);
|
81
|
+
int len = vsnprintf(NULL, 0, format, args);
|
82
|
+
va_end(args);
|
83
|
+
char *new_str = (char *)MG_ALLOC(pool, len + 1);
|
84
|
+
if (new_str == NULL) {
|
85
|
+
pool->error = &MONGORY_ALLOC_ERROR;
|
86
|
+
return NULL;
|
87
|
+
}
|
88
|
+
va_start(args, format);
|
89
|
+
vsnprintf(new_str, len + 1, format, args);
|
90
|
+
va_end(args);
|
91
|
+
new_str[len] = '\0';
|
92
|
+
return new_str;
|
93
|
+
}
|
94
|
+
|
95
|
+
double mongory_log(double x, double base) {
|
96
|
+
return log(x) / log(base);
|
97
|
+
}
|
98
|
+
|
99
|
+
bool mongory_validate_ptr(mongory_memory_pool *pool, char *name, void *ptr, char *file, int line) {
|
100
|
+
if (pool->error != NULL) {
|
101
|
+
return false;
|
102
|
+
}
|
103
|
+
if (ptr == NULL) {
|
104
|
+
mongory_error *error = MG_ALLOC_PTR(pool, mongory_error);
|
105
|
+
error->type = MONGORY_ERROR_INVALID_ARGUMENT;
|
106
|
+
error->message = mongory_string_cpyf(pool, "Null pointer: %s (at %s:%d)", name, file, line);
|
107
|
+
pool->error = error;
|
108
|
+
return false;
|
109
|
+
}
|
110
|
+
return true;
|
111
|
+
}
|
112
|
+
|
113
|
+
static char *error_format =
|
114
|
+
"[Mongory Core Error]\n"
|
115
|
+
"%s needs %s, got %s\n"
|
116
|
+
"(%s:%d)\n";
|
117
|
+
|
118
|
+
bool mongory_validate_table(mongory_memory_pool *pool, char *name, mongory_value *value, char *file, int line) {
|
119
|
+
if (pool->error != NULL) {
|
120
|
+
return false;
|
121
|
+
}
|
122
|
+
if (!mongory_validate_ptr(pool, name, value, file, line)) {
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
if (value->type != MONGORY_TYPE_TABLE) {
|
126
|
+
mongory_error *error = MG_ALLOC_PTR(pool, mongory_error);
|
127
|
+
error->type = MONGORY_ERROR_INVALID_ARGUMENT;
|
128
|
+
error->message = mongory_string_cpyf(pool, error_format,
|
129
|
+
name,
|
130
|
+
"Table",
|
131
|
+
mongory_type_to_string(value),
|
132
|
+
file,
|
133
|
+
line
|
134
|
+
);
|
135
|
+
pool->error = error;
|
136
|
+
return false;
|
137
|
+
}
|
138
|
+
return true;
|
139
|
+
}
|
140
|
+
|
141
|
+
bool mongory_validate_array(mongory_memory_pool *pool, char *name, mongory_value *value, char *file, int line) {
|
142
|
+
if (pool->error != NULL) {
|
143
|
+
return false;
|
144
|
+
}
|
145
|
+
if (!mongory_validate_ptr(pool, name, value, file, line)) {
|
146
|
+
return false;
|
147
|
+
}
|
148
|
+
if (value->type != MONGORY_TYPE_ARRAY) {
|
149
|
+
mongory_error *error = MG_ALLOC_PTR(pool, mongory_error);
|
150
|
+
error->type = MONGORY_ERROR_INVALID_ARGUMENT;
|
151
|
+
error->message = mongory_string_cpyf(pool, error_format,
|
152
|
+
name,
|
153
|
+
"Array",
|
154
|
+
mongory_type_to_string(value),
|
155
|
+
file,
|
156
|
+
line
|
157
|
+
);
|
158
|
+
pool->error = error;
|
159
|
+
return false;
|
160
|
+
}
|
161
|
+
return true;
|
162
|
+
}
|
163
|
+
|
164
|
+
bool mongory_validate_string(mongory_memory_pool *pool, char *name, mongory_value *value, char *file, int line) {
|
165
|
+
if (pool->error != NULL) {
|
166
|
+
return false;
|
167
|
+
}
|
168
|
+
if (!mongory_validate_ptr(pool, name, value, file, line)) {
|
169
|
+
return false;
|
170
|
+
}
|
171
|
+
if (value->type != MONGORY_TYPE_STRING) {
|
172
|
+
mongory_error *error = MG_ALLOC_PTR(pool, mongory_error);
|
173
|
+
error->type = MONGORY_ERROR_INVALID_ARGUMENT;
|
174
|
+
error->message = mongory_string_cpyf(pool, error_format,
|
175
|
+
name,
|
176
|
+
"String",
|
177
|
+
mongory_type_to_string(value),
|
178
|
+
file,
|
179
|
+
line
|
180
|
+
);
|
181
|
+
pool->error = error;
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
return true;
|
185
|
+
}
|
186
|
+
|
187
|
+
bool mongory_validate_number(mongory_memory_pool *pool, char *name, mongory_value *value, char *file, int line) {
|
188
|
+
if (pool->error != NULL) {
|
189
|
+
return false;
|
190
|
+
}
|
191
|
+
if (!mongory_validate_ptr(pool, name, value, file, line)) {
|
192
|
+
return false;
|
193
|
+
}
|
194
|
+
if (value->type != MONGORY_TYPE_INT && value->type != MONGORY_TYPE_DOUBLE) {
|
195
|
+
mongory_error *error = MG_ALLOC_PTR(pool, mongory_error);
|
196
|
+
error->type = MONGORY_ERROR_INVALID_ARGUMENT;
|
197
|
+
error->message = mongory_string_cpyf(pool, error_format,
|
198
|
+
name,
|
199
|
+
"Number",
|
200
|
+
mongory_type_to_string(value),
|
201
|
+
file,
|
202
|
+
line
|
203
|
+
);
|
204
|
+
pool->error = error;
|
205
|
+
return false;
|
206
|
+
}
|
207
|
+
return true;
|
208
|
+
}
|
209
|
+
|
210
|
+
#endif // MONGORY_UTILS_C
|