├── INSTALL ├── LICENSE ├── README ├── chdb.c ├── chdb.h ├── config.m4 ├── package.xml ├── php_chdb.c └── php_chdb.h /INSTALL: -------------------------------------------------------------------------------- 1 | To build and install chdb you need cmph 0.9 (http://cmph.sourceforge.net/). 2 | 3 | After that is done, you can build and install chdb using pear or you 4 | can choose a manual approach, building chdb by running: 5 | phpize 6 | ./configure 7 | make 8 | 9 | And finally installing it by: 10 | make install 11 | 12 | The configuration script tries to detect the location of cmph automatically. 13 | Should that fail, you can manually specify the prefix directory in which it's 14 | installed by running: 15 | ./configure --with-libcmph-dir=DIR 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010, Hyves (Startphone Ltd.) All rights reserved. 2 | 3 | Lorenzo Castelli 4 | Henk Punt 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Hyves (Startphone Ltd.) nor the names of its 17 | contributors may be used to endorse or promote products derived from this 18 | software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | chdb implements a read-only memory mapped hash-table for PHP. 2 | 3 | In PHP the only way to define large amounts of constants - for instance to store 4 | the association between language and text keys and the corresponding translation 5 | - is to use lots of defines, class consts, or create some humongous array 6 | structure. 7 | 8 | Even with an op-code cacher like APC or e-accelerator, defining constants this 9 | way is both CPU and memory consuming, as each instance of php will need to 10 | load/parse/execute the opcodes to define the constants for each request. 11 | 12 | chdb is a solution for this problem because: 13 | - It takes virtually no time to load initially. 14 | - Only the pages of the file which are actually used are read from the disk. 15 | - Once a page is loaded it is shared across all the processes in the same 16 | machine and cached across multiple requests. 17 | 18 | All these functionalities are provided by the operative system itself as 19 | features of memory mapped files. chdb thus provides to PHP something similar to 20 | what the data section provides to lower level programming languages like C. 21 | 22 | If you are using PHP in a typical setup for a big website which remains stable 23 | between releases you might benefit from the chdb extension. In that case you 24 | would create a disk file containing a hashmap of key-value pairs at deploy-time, 25 | and then map it into the memory of each php process at run-time. 26 | 27 | An example of use of chdb for this would be to run the following at deploy-time: 28 | $data = array( 29 | 'key1' => 'value1', 30 | 'key2' => 'value2', 31 | // ... 32 | ); 33 | chdb_create('data.chdb', $data); 34 | 35 | Then at run-time, the keys stored in the file can be read by: 36 | $chdb = new chdb('data.chdb'); 37 | $value1 = $chdb->get('key1'); 38 | $value2 = $chdb->get('key2'); -------------------------------------------------------------------------------- /chdb.c: -------------------------------------------------------------------------------- 1 | #include "chdb.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef HAVE_CONFIG_H 14 | # include "config.h" 15 | #endif 16 | 17 | #define CHDB_FILE_MAGIC htonl('c' << 24 | 'h' << 16 | 'd' << 8 | 'b') 18 | 19 | #ifdef WORDS_BIGENDIAN 20 | /* Flag the big endian version with 1 bit to signal incompatibility */ 21 | # define CHDB_FILE_VERSION (1u << 31 | CHDB_VERSION) 22 | #else 23 | # define CHDB_FILE_VERSION htonl(CHDB_VERSION) 24 | #endif 25 | 26 | /* Align entries on 4-byte boundaries */ 27 | #define CHDB_ALIGN_ORDER 2 28 | 29 | /** 30 | * Computes the first address of memory naturally aligned for the given 31 | * 'align_order' starting from 'address'. 32 | */ 33 | static inline size_t mem_align(size_t address, int align_order) { 34 | size_t mask = (1 << align_order) - 1; 35 | return (address + mask) & ~mask; 36 | } 37 | 38 | /** 39 | * The header of a chdb file. 40 | * - magic: chdb file magic number 41 | * - version: version of the file and endianness 42 | * - file_size: total size of the file in bytes 43 | * - table_offset: offset of the entry table from the beginning of the file 44 | * - entry_count: number of key-value pairs 45 | * - mph: packed cmph hashing function 46 | */ 47 | struct __chdb { 48 | uint32_t magic; 49 | uint32_t version; 50 | uint64_t file_size; 51 | uint32_t table_offset; 52 | uint32_t entry_count; 53 | char mph[]; 54 | } __attribute__((packed)); 55 | 56 | /** 57 | * Gets the entry table for a memory-mapped chdb file. 58 | * The entry table is an array of uint32_t with the offsets of 59 | * the chdb_entries from the beginning of the file indexed by 60 | * the hash of their keys. The offsets are shifted right by the 61 | * align order, so that we can effectively address 2^(32+align_order) 62 | * bytes of memory with 32 bit values - that is 16 GiB of memory. 63 | */ 64 | static inline uint32_t *chdb_get_table(chdb_t *chdb) 65 | { 66 | return (uint32_t *)((char *)chdb + chdb->table_offset); 67 | } 68 | 69 | static struct chdb_entry *chdb_get_entry(chdb_t *chdb, uint32_t idx) 70 | { 71 | uint32_t offset; 72 | if (idx >= chdb->entry_count) 73 | return NULL; 74 | 75 | offset = chdb_get_table(chdb)[idx]; 76 | return (struct chdb_entry *)((char *)chdb + 77 | (offset << CHDB_ALIGN_ORDER)); 78 | } 79 | 80 | /** 81 | * A key-value pair stored in a chdb file. 82 | * The key is stored just after the value. This layout was chosen 83 | * so that key_len, value_len and value are always aligned on 4-byte 84 | * boundaries. The key instead is not necessarily aligned, but it's 85 | * only used for a memcmp when doing a lookup. 86 | */ 87 | struct chdb_entry { 88 | uint32_t key_len; 89 | uint32_t value_len; 90 | char value[]; 91 | } __attribute__((packed)); 92 | 93 | static inline void *chdb_entry_get_key(struct chdb_entry *entry) 94 | { 95 | return entry->value + entry->value_len; 96 | } 97 | 98 | /** 99 | * Reads the chdb header and does some compatibility checks. 100 | * Sets the declared file size and returns 0 on success, or returns 101 | * an error code otherwise. 102 | */ 103 | static int chdb_read_header(int fd, uint64_t *file_size) 104 | { 105 | struct __chdb chdb; 106 | size_t rd = 0; 107 | 108 | do { 109 | int curr_rd = read(fd, (char*)&chdb + rd, sizeof(chdb) - rd); 110 | if (curr_rd > 0) 111 | rd += curr_rd; 112 | else if (curr_rd == 0) 113 | return EINVAL; 114 | else if (errno != EINTR) 115 | return errno; 116 | } while (rd < sizeof(chdb)); 117 | 118 | if (chdb.magic != CHDB_FILE_MAGIC || chdb.version != CHDB_FILE_VERSION 119 | /* check if the file is too big to mmap on this architecture */ 120 | || chdb.file_size > SIZE_MAX) 121 | return EINVAL; 122 | 123 | *file_size = chdb.file_size; 124 | return 0; 125 | } 126 | 127 | chdb_t *chdb_open(const char* pathname) 128 | { 129 | int fd, _errno = 0; 130 | uint64_t size = 0; 131 | chdb_t *chdb = NULL; 132 | 133 | if ((fd = open(pathname, O_RDONLY)) < 0) 134 | return NULL; /* errno is already set */ 135 | 136 | if ((_errno = chdb_read_header(fd, &size))) 137 | goto close_fd; 138 | 139 | if ((chdb = mmap(NULL, (size_t)size, PROT_READ, MAP_SHARED, fd, 0)) 140 | == MAP_FAILED) { 141 | _errno = errno; 142 | goto close_fd; 143 | } 144 | 145 | close_fd: 146 | close(fd); 147 | 148 | if (_errno) 149 | errno = _errno; 150 | return chdb; 151 | } 152 | 153 | void chdb_close(chdb_t *chdb) 154 | { 155 | munmap(chdb, (size_t)chdb->file_size); 156 | } 157 | 158 | int chdb_get(chdb_t *chdb, const void *key, uint32_t key_len, 159 | const void **value, uint32_t *value_len) 160 | { 161 | uint32_t idx; 162 | struct chdb_entry *entry; 163 | 164 | idx = cmph_search_packed(chdb->mph, key, key_len); 165 | entry = chdb_get_entry(chdb, idx); 166 | if (entry == NULL || entry->key_len != key_len || 167 | memcmp(chdb_entry_get_key(entry), key, key_len)) { 168 | errno = EINVAL; 169 | return -1; 170 | } 171 | 172 | *value = entry->value; 173 | *value_len = entry->value_len; 174 | return 0; 175 | } 176 | 177 | static int chdb_adapter_read(void *data, char **key, uint32_t *key_len) 178 | { 179 | const void *my_key, *value; 180 | uint32_t my_key_len, value_len; 181 | struct chdb_reader *reader = (struct chdb_reader*)data; 182 | 183 | reader->next(reader, &my_key, &my_key_len, &value, &value_len); 184 | /* Apparently the key should be copied before being passed to cmph */ 185 | if ((*key = malloc(my_key_len)) == NULL) { 186 | key = NULL; 187 | key_len = 0; 188 | return 0; 189 | } 190 | memcpy(*key, my_key, my_key_len); 191 | *key_len = my_key_len; 192 | return my_key_len; 193 | } 194 | 195 | static void chdb_adapter_dispose(void *data, char *key, uint32_t key_len) 196 | { 197 | free(key); 198 | } 199 | 200 | static void chdb_adapter_rewind(void *data) 201 | { 202 | struct chdb_reader *reader = (struct chdb_reader*)data; 203 | reader->rewind(reader); 204 | } 205 | 206 | /** 207 | * Generates a perfect hash function for the data provided by 'reader'. 208 | * Sets 'mph' to point to the function and returns 0, or returns 209 | * an error code otherwise. 210 | */ 211 | static int chdb_generate_hash(struct chdb_reader *reader, cmph_t **mph) 212 | { 213 | cmph_config_t *config; 214 | cmph_io_adapter_t adapter = { 215 | .data = reader, 216 | .nkeys = reader->count, 217 | .read = chdb_adapter_read, 218 | .dispose = chdb_adapter_dispose, 219 | .rewind = chdb_adapter_rewind 220 | }; 221 | 222 | /* cmph hangs in case of 0 keys and fails with 1 key */ 223 | if (adapter.nkeys <= 1) 224 | return EINVAL; 225 | 226 | if ((config = cmph_config_new(&adapter)) == NULL) 227 | return ENOMEM; 228 | 229 | cmph_config_set_algo(config, CMPH_CHD); 230 | *mph = cmph_new(config); 231 | cmph_config_destroy(config); 232 | 233 | return *mph != NULL ? 0 : ENOMEM; 234 | } 235 | 236 | /** 237 | * Writes a chdb file with the data provided by 'reader' and the hash 238 | * function 'mph'. 239 | * Returns 0 in case of success, or an error code otherwise. 240 | */ 241 | static int chdb_serialize(struct chdb_reader *reader, cmph_t *mph, FILE *out) 242 | { 243 | struct __chdb chdb; 244 | struct chdb_entry entry; 245 | uint32_t packed_size, hash, sh_pos; 246 | char *packed_mph; 247 | const void *key, *value; 248 | size_t written, table_offset; 249 | int i; 250 | long pos; 251 | 252 | if ((packed_size = cmph_packed_size(mph)) == 0 || 253 | (table_offset = mem_align(sizeof(chdb) + packed_size, 254 | CHDB_ALIGN_ORDER)) > UINT32_MAX) 255 | return EINVAL; 256 | 257 | /* Write header */ 258 | chdb.magic = CHDB_FILE_MAGIC; 259 | chdb.version = CHDB_FILE_VERSION; 260 | chdb.table_offset = (uint32_t)table_offset; 261 | chdb.entry_count = reader->count; 262 | if (fwrite(&chdb, 1, sizeof(chdb), out) < sizeof(chdb)) 263 | return EIO; 264 | 265 | /* Write mph */ 266 | if ((packed_mph = malloc(packed_size)) == NULL) 267 | return ENOMEM; 268 | 269 | cmph_pack(mph, packed_mph); 270 | written = fwrite(packed_mph, 1, packed_size, out); 271 | free(packed_mph); 272 | if (written < packed_size) 273 | return EIO; 274 | 275 | /* Skip the entry table */ 276 | if (fseek(out, table_offset + chdb.entry_count * sizeof(uint32_t), 277 | SEEK_SET)) 278 | return errno; 279 | 280 | /* Write entries and fill the entry table */ 281 | reader->rewind(reader); 282 | for (i = 0; i < chdb.entry_count; ++i) { 283 | reader->next(reader, &key, &entry.key_len, 284 | &value, &entry.value_len); 285 | hash = cmph_search(mph, key, entry.key_len); 286 | 287 | /* Put the current (aligned) offset in the entry table */ 288 | if ((pos = ftell(out)) == 0) 289 | return errno; 290 | pos = mem_align(pos, CHDB_ALIGN_ORDER); 291 | if ((pos >> CHDB_ALIGN_ORDER) > UINT32_MAX) 292 | return EINVAL; 293 | sh_pos = (uint32_t)(pos >> CHDB_ALIGN_ORDER); 294 | if (fseek(out, table_offset + hash * sizeof(uint32_t), SEEK_SET)) 295 | return errno; 296 | if (fwrite(&sh_pos, 1, sizeof(sh_pos), out) < sizeof(sh_pos)) 297 | return EIO; 298 | if (fseek(out, pos, SEEK_SET)) 299 | return errno; 300 | 301 | /* Write the entry */ 302 | if (fwrite(&entry, 1, sizeof(entry), out) < sizeof(entry) || 303 | fwrite(value, 1, entry.value_len, out) < entry.value_len || 304 | fwrite(key, 1, entry.key_len, out) < entry.key_len) 305 | return EIO; 306 | } 307 | 308 | /* Write the file size */ 309 | if ((chdb.file_size = ftell(out)) == 0 || 310 | fseek(out, (char *)&chdb.file_size - (char *)&chdb, SEEK_SET)) 311 | return errno; 312 | if (fwrite(&chdb.file_size, 1, sizeof(chdb.file_size), out) 313 | < sizeof(chdb.file_size)) 314 | return EIO; 315 | 316 | return 0; 317 | } 318 | 319 | int chdb_create(struct chdb_reader *reader, const char *pathname) 320 | { 321 | int _errno = 0; 322 | cmph_t *mph; 323 | FILE *out; 324 | 325 | if ((_errno = chdb_generate_hash(reader, &mph))) 326 | goto return_error; 327 | 328 | if ((out = fopen(pathname, "w")) == NULL) { 329 | _errno = errno; 330 | goto free_cmph; 331 | } 332 | 333 | _errno = chdb_serialize(reader, mph, out); 334 | 335 | if (fclose(out) == EOF && _errno == 0) 336 | _errno = errno; 337 | 338 | if (_errno) 339 | remove(pathname); /* Don't leave invalid files behind */ 340 | 341 | free_cmph: 342 | cmph_destroy(mph); 343 | 344 | return_error: 345 | if (_errno) { 346 | errno = _errno; 347 | return -1; 348 | } else 349 | return 0; 350 | } 351 | 352 | -------------------------------------------------------------------------------- /chdb.h: -------------------------------------------------------------------------------- 1 | #ifndef CHDB_H 2 | #define CHDB_H 3 | 4 | #include 5 | 6 | #define CHDB_VERSION 0 7 | 8 | typedef struct __chdb chdb_t; 9 | 10 | /** 11 | * Memory-maps a chdb file. 12 | * Returns a handle to the opened chdb, or sets errno and returns 13 | * NULL in case of error. 14 | */ 15 | chdb_t *chdb_open(const char *pathname); 16 | 17 | /** 18 | * Closes a memory-mapped chdb file. 19 | */ 20 | void chdb_close(chdb_t *chdb); 21 | 22 | /** 23 | * Performs a search for the given 'key' of length 'key_len' in 'chdb'. 24 | * Sets 'value' and 'value_len' and returns 0 in case of success, 25 | * or returns -1 and sets errno otherwise. 26 | * Note that the area of memory 'value' points to after a successful 27 | * call to this function is read-only. 28 | */ 29 | int chdb_get(chdb_t *chdb, const void *key, uint32_t key_len, 30 | const void **value, uint32_t *value_len); 31 | 32 | /** 33 | * Provides a set of key-value pairs to be indexed by chdb. 34 | * This structure should be filled in by the user code and passed to 35 | * chdb_create to generate a chdb file. 36 | * - private: a pointer to user data, ignored by chdb 37 | * - count: the number of key-value pairs to be indexed 38 | * - next: a pointer to the iterator function that provides the next 39 | * key-value pair 40 | * - rewind: a pointer to a function that should rewind the reader 41 | * back to the beginning of the set 42 | */ 43 | struct chdb_reader { 44 | void *private; 45 | uint32_t count; 46 | void (*next)(struct chdb_reader *reader, 47 | const void **key, uint32_t *key_len, 48 | const void **value, uint32_t *value_len); 49 | void (*rewind)(struct chdb_reader *reader); 50 | }; 51 | 52 | /** 53 | * Generates a chdb file at the given 'pathname' containing the data 54 | * provided by 'reader'. 55 | * Returns 0 in case of success, or sets errno and returns -1 otherwise. 56 | */ 57 | int chdb_create(struct chdb_reader *reader, const char *pathname); 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | PHP_ARG_WITH(chdb, whether to enable chdb support, 2 | [ --with-chdb Include chdb support]) 3 | 4 | PHP_ARG_WITH(libcmph-dir, for chdb, 5 | [ --with-libcmph-dir[=DIR] Set the path to libcmph install prefix.], yes) 6 | 7 | if test "$PHP_CHDB" != "no"; then 8 | SEARCH_PATH="/usr/local /usr" 9 | SEARCH_FOR="/include/cmph.h" 10 | if test -r $PHP_LIBCMPH_DIR/$SEARCH_FOR; then 11 | CMPH_DIR=$PHP_LIBCMPH_DIR 12 | else 13 | AC_MSG_CHECKING([for cmph files in default path]) 14 | for i in $SEARCH_PATH ; do 15 | if test -r $i/$SEARCH_FOR; then 16 | CMPH_DIR=$i 17 | AC_MSG_RESULT(found in $i) 18 | fi 19 | done 20 | fi 21 | 22 | if test -z "$CMPH_DIR"; then 23 | AC_MSG_RESULT([not found]) 24 | AC_MSG_ERROR([Cannot find cmph header (http://cmph.sourceforge.net/).]) 25 | fi 26 | 27 | PHP_ADD_INCLUDE($CMPH_DIR/include) 28 | 29 | LIBNAME=cmph 30 | LIBSYMBOL=cmph_new 31 | PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,,[ 32 | AC_MSG_ERROR([Cannot find cmph library (http://cmph.sourceforge.net/).]) 33 | ],[ 34 | -L$CMPH_DIR/lib -lm 35 | ]) 36 | 37 | PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $CMPH_DIR/lib, CHDB_SHARED_LIBADD) 38 | PHP_SUBST(CHDB_SHARED_LIBADD) 39 | 40 | AC_C_BIGENDIAN() 41 | PHP_NEW_EXTENSION(chdb, chdb.c php_chdb.c, $ext_shared, , "-Wall") 42 | fi 43 | 44 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | chdb 10 | pecl.php.net 11 | A fast database for constant data with memory sharing across processes 12 | CHDB (constant hash database) is a fast key-value database for constant data, 13 | realized by using a memory-mapped file and thus providing the following 14 | functionalities: 15 | - Extremely fast initial load, regardless of the size of the database. 16 | - Only the pages of the file which are actually used are loaded from the disk. 17 | - Once a page is loaded it is shared across multiple processes. 18 | - Loaded pages are cached across multiple requests and even process recycling. 19 | A typical use of CHDB is as a faster alternative to defining many PHP 20 | constants. 21 | CHDB is internally implemented as a hash-table using a perfect hashing function, 22 | thus guaranteeing worst case O(1) lookup time. 23 | 24 | Lorenzo Castelli 25 | lcastelli 26 | lcastelli@gmail.com 27 | yes 28 | 29 | 2013-04-07 30 | 31 | 1.0.3 32 | 1.0.3 33 | 34 | 35 | stable 36 | stable 37 | 38 | BSD 39 | - Use the right variable when adding the library path for CMPH (thanks to Jille Timmermans). 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 5.0.0 56 | 57 | 58 | 1.4.8 59 | 60 | 61 | unix 62 | 63 | 64 | 65 | chdb 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 1.0.3 74 | 1.0.3 75 | 76 | 77 | stable 78 | stable 79 | 80 | 2013-04-07 81 | - Use the right variable when adding the library path for CMPH (thanks to Jille Timmermans). 82 | 83 | 84 | 85 | 1.0.2 86 | 1.0.2 87 | 88 | 89 | stable 90 | stable 91 | 92 | 2012-04-18 93 | - Fix compilation for PHP 5.4 (thanks to Sebastian Volland). 94 | - Use RuntimeException instead of Exception for runtime errors. 95 | - Generate slightly better error in case of input size of 1. 96 | 97 | 98 | 99 | 1.0.1 100 | 1.0.1 101 | 102 | 103 | stable 104 | stable 105 | 106 | 2011-03-10 107 | - Fix bug #22600: possible crash when throwing an exception on 64-bit systems. 108 | - Fix decoding of error messages in some cases. 109 | 110 | 111 | 112 | 1.0.0 113 | 1.0.0 114 | 115 | 116 | stable 117 | stable 118 | 119 | 2011-03-10 120 | Bump version number to reflect stable state. 121 | 122 | 123 | 124 | 0.2.0 125 | 0.1.0 126 | 127 | 128 | beta 129 | beta 130 | 131 | 2010-07-21 132 | Allow unloading of chdb files. 133 | 134 | 135 | 136 | 0.1.0 137 | 0.1.0 138 | 139 | 140 | beta 141 | beta 142 | 143 | 2010-06-16 144 | Inital PECL release. 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /php_chdb.c: -------------------------------------------------------------------------------- 1 | // Include these first to ensure that we get the POSIX strerror_r 2 | #include 3 | #include 4 | 5 | #include "php_chdb.h" 6 | 7 | #ifdef ZTS 8 | # include 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "chdb.h" 16 | 17 | #ifdef COMPILE_DL_CHDB 18 | ZEND_GET_MODULE(chdb) 19 | #endif 20 | 21 | 22 | static void throw_except_errno(char *format, char *arg, int _errno TSRMLS_DC) 23 | { 24 | char buf[0x100]; 25 | if (strerror_r(_errno, buf, 0x100)) 26 | sprintf(buf, "Undefined error %d", _errno); 27 | zend_throw_exception_ex(spl_ce_RuntimeException, 28 | _errno TSRMLS_CC, format, arg, buf); 29 | } 30 | 31 | 32 | #define KEY_BUFFER_LEN 21 33 | struct php_chdb_reader_private { 34 | HashTable *data; 35 | HashPosition pos; 36 | zval val_copy; 37 | char key_buffer[KEY_BUFFER_LEN]; 38 | }; 39 | 40 | static void php_chdb_reader_next(struct chdb_reader *reader, 41 | const void **key, uint32_t *key_len, 42 | const void **value, uint32_t *value_len) 43 | { 44 | char *my_key; 45 | uint my_key_len; 46 | ulong idx; 47 | zval **cur; 48 | struct php_chdb_reader_private *private = reader->private; 49 | 50 | if (zend_hash_get_current_key_ex(private->data, &my_key, &my_key_len, 51 | &idx, 0, &private->pos) == HASH_KEY_IS_LONG) { 52 | /* convert the key to string */ 53 | my_key_len = snprintf(private->key_buffer, KEY_BUFFER_LEN, 54 | "%ld", idx); 55 | my_key = private->key_buffer; 56 | } else { 57 | /* ignore NULL string terminator */ 58 | my_key_len--; 59 | } 60 | 61 | /* convert the value to string */ 62 | zend_hash_get_current_data_ex(private->data, 63 | (void **)&cur, &private->pos); 64 | zval_dtor(&private->val_copy); /* delete the last copy */ 65 | private->val_copy = **cur; 66 | zval_copy_ctor(&private->val_copy); 67 | INIT_PZVAL(&private->val_copy); 68 | convert_to_string(&private->val_copy); 69 | 70 | *key = my_key; 71 | *key_len = my_key_len; 72 | *value = Z_STRVAL(private->val_copy); 73 | *value_len = Z_STRLEN(private->val_copy); 74 | 75 | zend_hash_move_forward_ex(private->data, &private->pos); 76 | } 77 | 78 | static void php_chdb_reader_rewind(struct chdb_reader *reader) 79 | { 80 | struct php_chdb_reader_private *private = reader->private; 81 | zend_hash_internal_pointer_reset_ex(private->data, &private->pos); 82 | } 83 | 84 | ZEND_BEGIN_ARG_INFO_EX(php_chdb_arginfo_create, 0, 0, 2) 85 | ZEND_ARG_INFO(0, pathname) 86 | ZEND_ARG_ARRAY_INFO(0, data, 0) 87 | ZEND_END_ARG_INFO() 88 | 89 | static PHP_FUNCTION(chdb_create) 90 | { 91 | char *pathname; 92 | uint pathname_len; 93 | zval *data; 94 | struct chdb_reader reader; 95 | struct php_chdb_reader_private private; 96 | int _errno = 0; 97 | 98 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", 99 | &pathname, &pathname_len, &data) == FAILURE) { 100 | zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 101 | 0 TSRMLS_CC, "Invalid parameters"); 102 | RETURN_FALSE; 103 | } 104 | 105 | private.data = Z_ARRVAL_P(data); 106 | zend_hash_internal_pointer_reset_ex(private.data, &private.pos); 107 | INIT_ZVAL(private.val_copy); 108 | 109 | reader.private = &private; 110 | reader.count = zend_hash_num_elements(private.data); 111 | reader.next = php_chdb_reader_next; 112 | reader.rewind = php_chdb_reader_rewind; 113 | 114 | if (chdb_create(&reader, pathname)) 115 | _errno = errno; 116 | 117 | zval_dtor(&private.val_copy); 118 | 119 | if (_errno) { 120 | throw_except_errno("Error generating '%s': %s", pathname, 121 | _errno TSRMLS_CC); 122 | RETURN_FALSE; 123 | } 124 | 125 | RETURN_TRUE; 126 | } 127 | 128 | const zend_function_entry php_chdb_functions[] = { 129 | PHP_FE(chdb_create, php_chdb_arginfo_create) 130 | { } 131 | }; 132 | 133 | 134 | static zend_class_entry *php_chdb_ce; 135 | 136 | struct php_chdb { 137 | zend_object zo; 138 | chdb_t *chdb; 139 | }; 140 | 141 | static void php_chdb_free_storage(struct php_chdb *intern TSRMLS_DC) 142 | { 143 | zend_object_std_dtor(&intern->zo TSRMLS_CC); 144 | if (intern->chdb) 145 | chdb_close(intern->chdb); 146 | efree(intern); 147 | } 148 | 149 | static zend_object_value php_chdb_new(zend_class_entry *ce TSRMLS_DC) 150 | { 151 | zend_object_value retval; 152 | struct php_chdb *intern; 153 | zval *tmp; 154 | 155 | intern = ecalloc(1, sizeof(*intern)); 156 | intern->chdb = NULL; 157 | zend_object_std_init(&intern->zo, ce TSRMLS_CC); 158 | #if PHP_VERSION_ID < 50399 159 | zend_hash_copy(intern->zo.properties, &ce->default_properties, 160 | (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *)); 161 | #else 162 | object_properties_init(&(intern->zo), ce); 163 | #endif 164 | 165 | retval.handle = zend_objects_store_put(intern, NULL, 166 | (zend_objects_free_object_storage_t)php_chdb_free_storage, 167 | NULL TSRMLS_CC); 168 | retval.handlers = zend_get_std_object_handlers(); 169 | 170 | return retval; 171 | } 172 | 173 | ZEND_BEGIN_ARG_INFO_EX(php_chdb_arginfo___construct, 0, 0, 1) 174 | ZEND_ARG_INFO(0, pathname) 175 | ZEND_END_ARG_INFO() 176 | 177 | static PHP_METHOD(chdb, __construct) 178 | { 179 | char *pathname; 180 | uint pathname_len; 181 | chdb_t *chdb; 182 | zval *object = getThis(); 183 | struct php_chdb *intern = (struct php_chdb *) 184 | zend_object_store_get_object(object TSRMLS_CC); 185 | 186 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", 187 | &pathname, &pathname_len) == FAILURE) { 188 | zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 189 | 0 TSRMLS_CC, "Invalid parameters"); 190 | RETURN_FALSE; 191 | } 192 | 193 | if ((chdb = chdb_open(pathname)) == NULL) { 194 | throw_except_errno("Cannot load '%s': %s", pathname, 195 | errno TSRMLS_CC); 196 | RETURN_FALSE; 197 | } 198 | 199 | intern->chdb = chdb; 200 | } 201 | 202 | ZEND_BEGIN_ARG_INFO_EX(php_chdb_arginfo_get, 0, 0, 1) 203 | ZEND_ARG_INFO(0, key) 204 | ZEND_END_ARG_INFO() 205 | 206 | static PHP_METHOD(chdb, get) 207 | { 208 | char *key, *value; 209 | uint key_len, value_len; 210 | zval *object = getThis(); 211 | struct php_chdb *intern = (struct php_chdb *) 212 | zend_object_store_get_object(object TSRMLS_CC); 213 | 214 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", 215 | &key, &key_len) == FAILURE) { 216 | zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 217 | 0 TSRMLS_CC, "Invalid parameters"); 218 | RETURN_NULL(); 219 | } 220 | 221 | if (chdb_get(intern->chdb, key, key_len, 222 | (const void **)&value, &value_len)) 223 | RETURN_NULL(); 224 | 225 | RETVAL_STRINGL(value, value_len, 1); 226 | } 227 | 228 | #define CHDB_ME(name, args) PHP_ME(chdb, name, args, ZEND_ACC_PUBLIC) 229 | static zend_function_entry php_chdb_class_methods[] = { 230 | CHDB_ME(__construct, php_chdb_arginfo___construct) 231 | CHDB_ME(get, php_chdb_arginfo_get) 232 | { } 233 | }; 234 | 235 | 236 | static PHP_MINIT_FUNCTION(chdb) 237 | { 238 | zend_class_entry ce; 239 | 240 | INIT_CLASS_ENTRY(ce, "chdb", php_chdb_class_methods); 241 | php_chdb_ce = zend_register_internal_class(&ce TSRMLS_CC); 242 | php_chdb_ce->create_object = php_chdb_new; 243 | 244 | return SUCCESS; 245 | } 246 | 247 | static PHP_MINFO_FUNCTION(chdb) 248 | { 249 | php_info_print_table_start(); 250 | php_info_print_table_header(2, "chdb support", "enabled"); 251 | php_info_print_table_row(2, "version", PHP_CHDB_VERSION); 252 | php_info_print_table_end(); 253 | } 254 | 255 | zend_module_entry chdb_module_entry = { 256 | #if ZEND_MODULE_API_NO >= 20010901 257 | STANDARD_MODULE_HEADER, 258 | #endif 259 | "chdb", 260 | php_chdb_functions, 261 | PHP_MINIT(chdb), 262 | NULL, 263 | NULL, 264 | NULL, 265 | PHP_MINFO(chdb), 266 | #if ZEND_MODULE_API_NO >= 20010901 267 | PHP_CHDB_VERSION, 268 | #endif 269 | STANDARD_MODULE_PROPERTIES 270 | }; 271 | 272 | -------------------------------------------------------------------------------- /php_chdb.h: -------------------------------------------------------------------------------- 1 | /* This module exposes to PHP the equivalent of: 2 | * // Creates a chdb file containing the key-value pairs specified in the 3 | * // array $data, or throws an exception in case of error. 4 | * function chdb_create($pathname, $data); 5 | * 6 | * // Represents a loaded chdb file. 7 | * class chdb 8 | * { 9 | * // Loads a chdb file, or throws an exception in case of error. 10 | * public function __construct($pathname); 11 | * 12 | * // Returns the value of the given $key, or null if not found. 13 | * public function get($key); 14 | * } 15 | */ 16 | 17 | #ifndef PHP_CHDB_H 18 | #define PHP_CHDB_H 19 | 20 | #ifdef HAVE_CONFIG_H 21 | # include "config.h" 22 | #endif 23 | 24 | #include 25 | 26 | #define PHP_CHDB_VERSION "1.0.1" 27 | 28 | extern zend_module_entry chdb_module_entry; 29 | #define phpext_chdb_ptr &chdb_module_entry 30 | 31 | #endif 32 | 33 | --------------------------------------------------------------------------------