├── .gitignore ├── example ├── demovfs │ └── CMakeLists.txt ├── CMakeLists.txt ├── 3.c ├── 1.c ├── unqlite_huge.c ├── unqlite_tar.c ├── 4.c ├── 2.c ├── unqlite_mp3.c ├── 6.c └── 5.c ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG.md ├── .github └── FUNDING.yml ├── CMakeLists.txt ├── src ├── license.txt ├── jx9_license.txt ├── os.c ├── bitvec.c ├── fastjson.c ├── unqliteInt.h ├── mem_kv.c └── jx9_json.c ├── LICENSE ├── README.md └── update-md5.py /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake / Make / Build 2 | /CMakeFiles/ 3 | cmake_install.cmake 4 | CMakeCache.txt 5 | Makefile 6 | 7 | # Binary 8 | libunqlite.a 9 | 10 | # IDEs 11 | .idea 12 | .vscode 13 | 14 | # Build 15 | *build* 16 | -------------------------------------------------------------------------------- /example/demovfs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXEC_NAME "${PROJECT_NAME}_test_example_unqlite_demovfs") 2 | set(Source_Files "unqlite_demovfs.c") 3 | source_group("${EXEC_NAME} Source Files" FILES "${Source_Files}") 4 | add_executable("${EXEC_NAME}" "${Source_Files}") 5 | target_link_libraries("${EXEC_NAME}" PUBLIC ${UNQLITE_STATIC_LIB}) 6 | add_test(NAME "${EXEC_NAME}" COMMAND "${EXEC_NAME}") 7 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - test 4 | 5 | build:linux: 6 | stage: build 7 | image: ubuntu:17.10 8 | tags: 9 | - docker 10 | before_script: 11 | - DEBIAN_FRONTEND=noninteractive apt-get update -y 12 | - DEBIAN_FRONTEND=noninteractive apt-get install -y gcc-mingw-w64 build-essential cmake 13 | script: 14 | - cmake ./CMakeLists.txt 15 | - make all 16 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | foreach (name "1" "2" "3" "4" "5" "6" "unqlite_huge" "unqlite_mp3" "unqlite_tar") 2 | set(EXEC_NAME "${PROJECT_NAME}_test_example_${name}") 3 | set(Source_Files "${name}.c") 4 | source_group("${EXEC_NAME} Source Files" FILES "${Source_Files}") 5 | add_executable("${EXEC_NAME}" "${Source_Files}") 6 | target_link_libraries("${EXEC_NAME}" PUBLIC ${UNQLITE_STATIC_LIB}) 7 | add_test(NAME "${EXEC_NAME}" COMMAND "${EXEC_NAME}") 8 | endforeach () 9 | 10 | add_subdirectory(demovfs) 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | os: linux 3 | dist: trusty 4 | 5 | # Language 6 | language: cpp 7 | 8 | matrix: 9 | include: 10 | # GCC 6 11 | - env: UNIT_TESTS=true COMPILER=g++-6 BOOST_VERSION=default ENABLE_MEMCHECK=true 12 | addons: { apt: { packages: ["g++-6", "valgrind"], sources: ["ubuntu-toolchain-r-test"] } } 13 | 14 | # GCC 7 15 | - env: UNIT_TESTS=true COMPILER=g++-7 BOOST_VERSION=default ENABLE_MEMCHECK=true 16 | addons: { apt: { packages: ["g++-7", "valgrind"], sources: ["ubuntu-toolchain-r-test"] } } 17 | 18 | script: 19 | - | 20 | cmake CMakeLists.txt 21 | make all -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | ### [v1.1.8] - May 2019 8 | 9 | ### Fixed 10 | - Minor file handle leak fixed described in [issue 74](https://github.com/symisc/unqlite/issues/74). 11 | ### State 12 | - No data corruption bug found since December 2017. 13 | ## [1.1.8] - January 2018 14 | 15 | ### Fixed 16 | 17 | - Sync database first if a dirty commit has been applied. 18 | - Header inclusion fix. 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [symisc] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) 2 | PROJECT(unqlite) 3 | INCLUDE(GNUInstallDirs) 4 | 5 | # If the user did not customize the install prefix, 6 | # set it to live under build so we don't inadvertently pollute /usr/local 7 | IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 8 | SET(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "default install path" FORCE) 9 | ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 10 | 11 | OPTION(ENABLE_THREADS "Enable threads" OFF) 12 | 13 | IF(${ENABLE_THREADS}) 14 | ADD_DEFINITIONS("-DUNQLITE_ENABLE_THREADS") 15 | ENDIF(${ENABLE_THREADS}) 16 | 17 | INCLUDE_DIRECTORIES(${CMAKE_CURRENT_LIST_DIR}/src) 18 | 19 | SET(HEADERS_UNQLITE 20 | unqlite.h 21 | ) 22 | source_group("Header Files" FILES "${HEADERS_UNQLITE}") 23 | 24 | SET(SOURCES_UNQLITE 25 | unqlite.c 26 | ) 27 | source_group("Source Files" FILES "${SOURCES_UNQLITE}") 28 | 29 | SET(UNQLITE_STATIC_LIB unqlite) 30 | ADD_LIBRARY(${UNQLITE_STATIC_LIB} ${HEADERS_UNQLITE} ${SOURCES_UNQLITE}) 31 | 32 | INSTALL(TARGETS ${UNQLITE_STATIC_LIB} COMPONENT devel ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") 33 | INSTALL(FILES ${HEADERS_UNQLITE} COMPONENT devel DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 34 | 35 | include(CTest) 36 | if (BUILD_TESTING) 37 | add_subdirectory("example") 38 | endif (BUILD_TESTING) 39 | -------------------------------------------------------------------------------- /src/license.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 17 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS 18 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 21 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 23 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 24 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | * Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 16 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 20 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 22 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /src/jx9_license.txt: -------------------------------------------------------------------------------- 1 | --BEGIN --SYMISC PUBLIC LICENSE--- 2 | /* 3 | * Copyright (C) 2012, 2013 Symisc Systems. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Redistributions in any form must be accompanied by information on 14 | * how to obtain complete source code for the JX9 engine and any 15 | * accompanying software that uses the JX9 engine software. 16 | * The source code must either be included in the distribution 17 | * or be available for no more than the cost of distribution plus 18 | * a nominal fee, and must be freely redistributable under reasonable 19 | * conditions. For an executable file, complete source code means 20 | * the source code for all modules it contains.It does not include 21 | * source code for modules or files that typically accompany the major 22 | * components of the operating system on which the executable file runs. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS 25 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 27 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS 28 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 31 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 32 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 34 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | --END -- SYMISC PUBLIC LICENSE--- 37 | 38 | It is possible to circumvent this licensing policy through the purchase of a commercial software 39 | license from Symisc Systems consisting of terms and conditions which are negotiated at the time 40 | of sale. 41 | For more information, please contact Symisc Systems at: 42 | licensing@symisc.net 43 | or visit: 44 | http://jx9.symisc.net/licensing.html 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### UnQLite - An Embedded Transactional Database (Key/Value & Document Store) Engine - https://unqlite.symisc.net 2 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/symisc/unqlite/pulse) [![GitHub license](https://img.shields.io/pypi/l/Django.svg)](https://unqlite.symisc.net/licensing.html) 3 | 4 | **Release 1.1.9** (May 2024): New Homepage - https://unqlite.symisc.net 5 | 6 | **Release 1.1.9** (April 2022): Shared database (between one legit & one corrupt process) bug fix: https://github.com/symisc/unqlite/issues/137 7 | 8 | As of January 2018 - Symisc Systems has decided to **revive the UnQLite project**. All known data corruption bugs have been fixed, and expect to see new features (LZ compression), performance improvements, etc to be pushed here. 9 | You should rely for your production build on the amalgamation file and its header file available here or to be downloaded directly from https://unqlite.symisc.net/downloads.html 10 | 11 | 12 | UnQLite is a in-process software library which implements a self-contained, serverless, zero-configuration, transactional NoSQL database engine. UnQLite is a document store database similar to MongoDB, Redis, CouchDB etc. as well a standard Key/Value store similar to BerkeleyDB, LevelDB, etc. 13 | 14 | 15 | UnQLite is an embedded NoSQL (Key/Value store and Document-store) database engine. Unlike most other NoSQL databases, UnQLite does not have a separate server process. UnQLite reads and writes directly to ordinary disk files. A complete database with multiple collections, is contained in a single disk file. The database file format is cross-platform, you can freely copy a database between 32-bit and 64-bit systems or between big-endian and little-endian architectures. UnQLite features includes: 16 | 17 | 18 | Serverless, NoSQL database engine. 19 | Transactional (ACID) database. 20 | Zero configuration. 21 | Single database file, does not use temporary files. 22 | Cross-platform file format. 23 | UnQLite is a Self-Contained C library without dependency. 24 | Standard Key/Value store. 25 | Document store (JSON) database via Jx9. 26 | Support cursors for linear records traversal. 27 | Pluggable run-time interchangeable storage engine. 28 | Support for on-disk as well in-memory databases. 29 | Built with a powerful disk storage engine which support O(1) lookup. 30 | Thread safe and full reentrant. 31 | Simple, Clean and easy to use API. 32 | Support Terabyte sized databases. 33 | BSD licensed product. 34 | Amalgamation: All C source code for UnQLite and Jx9 are combined into a single source file. 35 | 36 | 37 | 38 | UnQLite is a self-contained C library without dependency. It requires very minimal support from external libraries or from the operating system. This makes it well suited for use in embedded devices that lack the support infrastructure of a desktop computer. This also makes UnQLite appropriate for use within applications that need to run without modification on a wide variety of computers of varying configurations. 39 | 40 | UnQLite is written in ANSI C, Thread-safe, Full reentrant, compiles unmodified and should run in most platforms including restricted embedded devices with a C compiler. UnQLite is extensively tested on Windows and UNIX systems especially Linux, FreeBSD, Oracle Solaris and Mac OS X. 41 | 42 | 43 | https://unqlite.symisc.net 44 | -------------------------------------------------------------------------------- /src/os.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. 3 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ 4 | * Version 1.1.6 5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES 6 | * please contact Symisc Systems via: 7 | * legal@symisc.net 8 | * licensing@symisc.net 9 | * contact@symisc.net 10 | * or visit: 11 | * http://unqlite.org/licensing.html 12 | */ 13 | /* $SymiscID: os.c v1.0 FreeBSD 2012-11-12 21:27 devel $ */ 14 | #ifndef UNQLITE_AMALGAMATION 15 | #include "unqliteInt.h" 16 | #endif 17 | /* OS interfaces abstraction layers: Mostly SQLite3 source tree */ 18 | /* 19 | ** The following routines are convenience wrappers around methods 20 | ** of the unqlite_file object. This is mostly just syntactic sugar. All 21 | ** of this would be completely automatic if UnQLite were coded using 22 | ** C++ instead of plain old C. 23 | */ 24 | UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset) 25 | { 26 | return id->pMethods->xRead(id, pBuf, amt, offset); 27 | } 28 | UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset) 29 | { 30 | return id->pMethods->xWrite(id, pBuf, amt, offset); 31 | } 32 | UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size) 33 | { 34 | return id->pMethods->xTruncate(id, size); 35 | } 36 | UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags) 37 | { 38 | return id->pMethods->xSync(id, flags); 39 | } 40 | UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize) 41 | { 42 | return id->pMethods->xFileSize(id, pSize); 43 | } 44 | UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType) 45 | { 46 | return id->pMethods->xLock(id, lockType); 47 | } 48 | UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType) 49 | { 50 | return id->pMethods->xUnlock(id, lockType); 51 | } 52 | UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut) 53 | { 54 | return id->pMethods->xCheckReservedLock(id, pResOut); 55 | } 56 | UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id) 57 | { 58 | if( id->pMethods->xSectorSize ){ 59 | return id->pMethods->xSectorSize(id); 60 | } 61 | return UNQLITE_DEFAULT_SECTOR_SIZE; 62 | } 63 | /* 64 | ** The next group of routines are convenience wrappers around the 65 | ** VFS methods. 66 | */ 67 | UNQLITE_PRIVATE int unqliteOsOpen( 68 | unqlite_vfs *pVfs, 69 | SyMemBackend *pAlloc, 70 | const char *zPath, 71 | unqlite_file **ppOut, 72 | unsigned int flags 73 | ) 74 | { 75 | unqlite_file *pFile; 76 | int rc; 77 | *ppOut = 0; 78 | if( zPath == 0 ){ 79 | /* May happen if dealing with an in-memory database */ 80 | return SXERR_EMPTY; 81 | } 82 | /* Allocate a new instance */ 83 | pFile = (unqlite_file *)SyMemBackendAlloc(pAlloc,sizeof(unqlite_file)+pVfs->szOsFile); 84 | if( pFile == 0 ){ 85 | return UNQLITE_NOMEM; 86 | } 87 | /* Zero the structure */ 88 | SyZero(pFile,sizeof(unqlite_file)+pVfs->szOsFile); 89 | /* Invoke the xOpen method of the underlying VFS */ 90 | rc = pVfs->xOpen(pVfs, zPath, pFile, flags); 91 | if( rc != UNQLITE_OK ){ 92 | SyMemBackendFree(pAlloc,pFile); 93 | pFile = 0; 94 | } 95 | *ppOut = pFile; 96 | return rc; 97 | } 98 | UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId) 99 | { 100 | int rc = UNQLITE_OK; 101 | if( pId ){ 102 | rc = pId->pMethods->xClose(pId); 103 | SyMemBackendFree(pAlloc,pId); 104 | } 105 | return rc; 106 | } 107 | UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync){ 108 | return pVfs->xDelete(pVfs, zPath, dirSync); 109 | } 110 | UNQLITE_PRIVATE int unqliteOsAccess( 111 | unqlite_vfs *pVfs, 112 | const char *zPath, 113 | int flags, 114 | int *pResOut 115 | ){ 116 | return pVfs->xAccess(pVfs, zPath, flags, pResOut); 117 | } 118 | -------------------------------------------------------------------------------- /update-md5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding:utf-8 -*- 3 | # 4 | # Copyright 2013 buaa.byl@gmail.com 5 | # 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2, or (at your option) 10 | # any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; see the file COPYING. If not, write to 19 | # the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 20 | # 21 | from __future__ import print_function 22 | import copy 23 | import sys 24 | import os 25 | import re 26 | from hashlib import md5 27 | 28 | regex_file = re.compile(r'^[ *]+File: ([0-9a-zA-Z_.-]+)\W*$') 29 | regex_md5 = re.compile(r'^[ *]+(MD5): ([0-9a-fA-F]+)\W*$') 30 | regex_id = re.compile(r'^[ *]+(ID): ([0-9a-fA-F]+)\W*$') 31 | 32 | HEADER = '''\ 33 | /* 34 | * ---------------------------------------------------------- 35 | * File: %(filename)s 36 | * %(type)s: %(hash)s 37 | * ---------------------------------------------------------- 38 | */\ 39 | ''' 40 | 41 | 42 | def file_get_contents(fn): 43 | f = open(fn, 'r') 44 | d = f.read() 45 | f.close() 46 | return d 47 | 48 | def file_put_contents(fn, d): 49 | f = open(fn, 'w') 50 | f.write(d) 51 | f.close() 52 | 53 | def new_info(INFO, lineno, filename, newtype, newhash): 54 | info = copy.deepcopy(INFO) 55 | info['lineno'] = lineno + 1 56 | info['filename']= filename 57 | info['type'] = newtype 58 | info['oldhash'] = newhash 59 | return info 60 | 61 | def build_context(info, lines): 62 | text = '\n'.join(lines) 63 | hashstr = md5(text).hexdigest() 64 | info['hash'] = hashstr 65 | info['range'] = (info['lineno'], info['lineno'] + len(lines) - 1) 66 | info['text'] = text 67 | return info 68 | 69 | if __name__ == '__main__': 70 | source_fn = sys.argv[1] 71 | v = file_get_contents(source_fn) 72 | 73 | print('parsing...', source_fn) 74 | print() 75 | 76 | INFO = { 77 | 'lineno' :0, 78 | 'range' :(0, ), 79 | 'filename' :'', 80 | 'type' :'', 81 | 'oldhash' :'', 82 | 'hash' :'', 83 | 'text' :'' 84 | } 85 | 86 | lines = v.splitlines() 87 | nr_lines = len(lines) 88 | 89 | lst_unpacked = [] 90 | 91 | lineno = 0 92 | file_context = [] 93 | 94 | info = new_info(INFO, lineno, 'amalgamation.h', '', '') 95 | 96 | while lineno < nr_lines: 97 | line = lines[lineno] 98 | 99 | if line.find('END-OF-IMPLEMENTATION:') >= 0: 100 | if len(file_context) > 0: 101 | lst_unpacked.append(build_context(info, file_context)) 102 | info = new_info(INFO, lineno, 'amalgamation.c', newtype, newhash) 103 | file_context = lines[lineno:] 104 | break 105 | 106 | res = regex_file.match(line) 107 | if res: 108 | fn_new = res.groups()[0] 109 | line = lines[lineno + 1] 110 | res = regex_md5.match(line) 111 | if not res: 112 | res = regex_id.match(line) 113 | if not res: 114 | file_context.append(lines[lineno]) 115 | lineno += 1 116 | continue 117 | 118 | newtype = res.groups()[0] 119 | newhash = res.groups()[1] 120 | lineno += 4 121 | 122 | file_context.pop(-1) 123 | file_context.pop(-1) 124 | 125 | if len(file_context) > 0: 126 | lst_unpacked.append(build_context(info, file_context)) 127 | info = new_info(INFO, lineno, fn_new, newtype, newhash) 128 | file_context = [] 129 | continue 130 | 131 | file_context.append(line) 132 | lineno += 1 133 | 134 | if len(file_context) > 0: 135 | lst_unpacked.append(build_context(info, file_context)) 136 | 137 | for db in lst_unpacked: 138 | print('%s:%d %d-%d' % (sys.argv[1], db['lineno'], db['range'][0], db['range'][1])) 139 | print('File: %s' % db['filename']) 140 | print('MD5 : %s %s parsed' % (db['type'], db['oldhash'])) 141 | print('MD5 : %s %s calculated' % (db['type'], db['hash'])) 142 | print() 143 | 144 | if len(sys.argv) == 3: 145 | target_dir = sys.argv[2] 146 | for db in lst_unpacked: 147 | fn = os.path.join(target_dir, db['filename']) 148 | file_put_contents(fn, db['text']) 149 | print('wrote %s' % fn) 150 | print() 151 | 152 | l = [] 153 | for db in lst_unpacked: 154 | if db['filename'].startswith('amalgamation'): 155 | l.append(db['text']) 156 | else: 157 | l.append(HEADER % db) 158 | l.append(db['text']) 159 | l.append('') 160 | 161 | fn = source_fn + '.__regenerate__.c' 162 | print('wrote %s' % fn) 163 | file_put_contents(fn, '\n'.join(l)) 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/bitvec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. 3 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ 4 | * Version 1.1.6 5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES 6 | * please contact Symisc Systems via: 7 | * legal@symisc.net 8 | * licensing@symisc.net 9 | * contact@symisc.net 10 | * or visit: 11 | * http://unqlite.org/licensing.html 12 | */ 13 | /* $SymiscID: bitvec.c v1.0 Win7 2013-02-27 15:16 stable $ */ 14 | #ifndef UNQLITE_AMALGAMATION 15 | #include "unqliteInt.h" 16 | #endif 17 | 18 | /** This file implements an object that represents a dynmaic 19 | ** bitmap. 20 | ** 21 | ** A bitmap is used to record which pages of a database file have been 22 | ** journalled during a transaction, or which pages have the "dont-write" 23 | ** property. Usually only a few pages are meet either condition. 24 | ** So the bitmap is usually sparse and has low cardinality. 25 | */ 26 | /* 27 | * Actually, this is not a bitmap but a simple hashtable where page 28 | * number (64-bit unsigned integers) are used as the lookup keys. 29 | */ 30 | typedef struct bitvec_rec bitvec_rec; 31 | struct bitvec_rec 32 | { 33 | pgno iPage; /* Page number */ 34 | bitvec_rec *pNext,*pNextCol; /* Collison link */ 35 | }; 36 | struct Bitvec 37 | { 38 | SyMemBackend *pAlloc; /* Memory allocator */ 39 | sxu32 nRec; /* Total number of records */ 40 | sxu32 nSize; /* Table size */ 41 | bitvec_rec **apRec; /* Record table */ 42 | bitvec_rec *pList; /* List of records */ 43 | }; 44 | /* 45 | * Allocate a new bitvec instance. 46 | */ 47 | UNQLITE_PRIVATE Bitvec * unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize) 48 | { 49 | bitvec_rec **apNew; 50 | Bitvec *p; 51 | 52 | p = (Bitvec *)SyMemBackendAlloc(pAlloc,sizeof(*p) ); 53 | if( p == 0 ){ 54 | SXUNUSED(iSize); /* cc warning */ 55 | return 0; 56 | } 57 | /* Zero the structure */ 58 | SyZero(p,sizeof(Bitvec)); 59 | /* Allocate a new table */ 60 | p->nSize = 64; /* Must be a power of two */ 61 | apNew = (bitvec_rec **)SyMemBackendAlloc(pAlloc,p->nSize * sizeof(bitvec_rec *)); 62 | if( apNew == 0 ){ 63 | SyMemBackendFree(pAlloc,p); 64 | return 0; 65 | } 66 | /* Zero the new table */ 67 | SyZero((void *)apNew,p->nSize * sizeof(bitvec_rec *)); 68 | /* Fill-in */ 69 | p->apRec = apNew; 70 | p->pAlloc = pAlloc; 71 | return p; 72 | } 73 | /* 74 | * Check if the given page number is already installed in the table. 75 | * Return true if installed. False otherwise. 76 | */ 77 | UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i) 78 | { 79 | bitvec_rec *pRec; 80 | /* Point to the desired bucket */ 81 | pRec = p->apRec[i & (p->nSize - 1)]; 82 | for(;;){ 83 | if( pRec == 0 ){ break; } 84 | if( pRec->iPage == i ){ 85 | /* Page found */ 86 | return 1; 87 | } 88 | /* Point to the next entry */ 89 | pRec = pRec->pNextCol; 90 | 91 | if( pRec == 0 ){ break; } 92 | if( pRec->iPage == i ){ 93 | /* Page found */ 94 | return 1; 95 | } 96 | /* Point to the next entry */ 97 | pRec = pRec->pNextCol; 98 | 99 | 100 | if( pRec == 0 ){ break; } 101 | if( pRec->iPage == i ){ 102 | /* Page found */ 103 | return 1; 104 | } 105 | /* Point to the next entry */ 106 | pRec = pRec->pNextCol; 107 | 108 | 109 | if( pRec == 0 ){ break; } 110 | if( pRec->iPage == i ){ 111 | /* Page found */ 112 | return 1; 113 | } 114 | /* Point to the next entry */ 115 | pRec = pRec->pNextCol; 116 | } 117 | /* No such entry */ 118 | return 0; 119 | } 120 | /* 121 | * Install a given page number in our bitmap (Actually, our hashtable). 122 | */ 123 | UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i) 124 | { 125 | bitvec_rec *pRec; 126 | sxi32 iBuck; 127 | /* Allocate a new instance */ 128 | pRec = (bitvec_rec *)SyMemBackendPoolAlloc(p->pAlloc,sizeof(bitvec_rec)); 129 | if( pRec == 0 ){ 130 | return UNQLITE_NOMEM; 131 | } 132 | /* Zero the structure */ 133 | SyZero(pRec,sizeof(bitvec_rec)); 134 | /* Fill-in */ 135 | pRec->iPage = i; 136 | iBuck = i & (p->nSize - 1); 137 | pRec->pNextCol = p->apRec[iBuck]; 138 | p->apRec[iBuck] = pRec; 139 | pRec->pNext = p->pList; 140 | p->pList = pRec; 141 | p->nRec++; 142 | if( p->nRec >= (p->nSize * 3) && p->nRec < 100000 ){ 143 | /* Grow the hashtable */ 144 | sxu32 nNewSize = p->nSize << 1; 145 | bitvec_rec *pEntry,**apNew; 146 | sxu32 n; 147 | apNew = (bitvec_rec **)SyMemBackendAlloc(p->pAlloc, nNewSize * sizeof(bitvec_rec *)); 148 | if( apNew ){ 149 | sxu32 iBucket; 150 | /* Zero the new table */ 151 | SyZero((void *)apNew, nNewSize * sizeof(bitvec_rec *)); 152 | /* Rehash all entries */ 153 | n = 0; 154 | pEntry = p->pList; 155 | for(;;){ 156 | /* Loop one */ 157 | if( n >= p->nRec ){ 158 | break; 159 | } 160 | pEntry->pNextCol = 0; 161 | /* Install in the new bucket */ 162 | iBucket = pEntry->iPage & (nNewSize - 1); 163 | pEntry->pNextCol = apNew[iBucket]; 164 | apNew[iBucket] = pEntry; 165 | /* Point to the next entry */ 166 | pEntry = pEntry->pNext; 167 | n++; 168 | } 169 | /* Release the old table and reflect the change */ 170 | SyMemBackendFree(p->pAlloc,(void *)p->apRec); 171 | p->apRec = apNew; 172 | p->nSize = nNewSize; 173 | } 174 | } 175 | return UNQLITE_OK; 176 | } 177 | /* 178 | * Destroy a bitvec instance. Reclaim all memory used. 179 | */ 180 | UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p) 181 | { 182 | bitvec_rec *pNext,*pRec = p->pList; 183 | SyMemBackend *pAlloc = p->pAlloc; 184 | 185 | for(;;){ 186 | if( p->nRec < 1 ){ 187 | break; 188 | } 189 | pNext = pRec->pNext; 190 | SyMemBackendPoolFree(pAlloc,(void *)pRec); 191 | pRec = pNext; 192 | p->nRec--; 193 | 194 | if( p->nRec < 1 ){ 195 | break; 196 | } 197 | pNext = pRec->pNext; 198 | SyMemBackendPoolFree(pAlloc,(void *)pRec); 199 | pRec = pNext; 200 | p->nRec--; 201 | 202 | 203 | if( p->nRec < 1 ){ 204 | break; 205 | } 206 | pNext = pRec->pNext; 207 | SyMemBackendPoolFree(pAlloc,(void *)pRec); 208 | pRec = pNext; 209 | p->nRec--; 210 | 211 | 212 | if( p->nRec < 1 ){ 213 | break; 214 | } 215 | pNext = pRec->pNext; 216 | SyMemBackendPoolFree(pAlloc,(void *)pRec); 217 | pRec = pNext; 218 | p->nRec--; 219 | } 220 | SyMemBackendFree(pAlloc,(void *)p->apRec); 221 | SyMemBackendFree(pAlloc,p); 222 | } 223 | -------------------------------------------------------------------------------- /example/3.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_csr_intro.c unqlite.c -o unqlite_csr 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Using Database Cursors: 12 | * 13 | * Cursors provide a mechanism by which you can iterate over the records 14 | * in a database. Using cursors, you can seek, fetch, move, and delete 15 | * database records. 16 | * 17 | * Before playing with cursors, you must first allocate a new cursor 18 | * handle using unqlite_kv_cursor_init(). 19 | * This is often the first UnQLite cursor API call that an application 20 | * makes and is a prerequisite in order to use cursors. When done, you must 21 | * call unqlite_kv_cursor_release() to release any allocated resource by 22 | * the cursor and thus to avoid memory leaks. 23 | * 24 | * To iterate over database records, from the first record to the last, simply 25 | * call unqlite_kv_cursor_first_entry() with successive call to unqlite_kv_cursor_next_entry() 26 | * until it return a value other than UNQLITE_OK (See example below). 27 | * Note that you can call unqlite_kv_cursor_valid_entry() to check if the cursor 28 | * is pointing to a valid record (This will return 1 when valid. 0 otherwise). 29 | * 30 | * You can also use cursors to search for records and start the iteration process 31 | * from there. To do that, simply call unqlite_kv_cursor_seek() with the target 32 | * record key and the seek direction (Last argument)... 33 | * 34 | * For an introduction to the UnQLite cursor interface, please refer to: 35 | * http://unqlite.org/c_api/unqlite_kv_cursor.html 36 | * For an introduction to the UnQLite C/C++ interface, please refer to: 37 | * http://unqlite.org/api_intro.html 38 | * For the full C/C++ API reference guide, please refer to: 39 | * http://unqlite.org/c_api.html 40 | * UnQLite in 5 Minutes or Less: 41 | * http://unqlite.org/intro.html 42 | * The Architecture of the UnQLite Database Engine: 43 | * http://unqlite.org/arch.html 44 | * For an introduction to Jx9 which is the scripting language which power 45 | * the Document-Store interface to UnQLite, please refer to: 46 | * http://unqlite.org/jx9.html 47 | */ 48 | /* $SymiscID: unqlite_csr_intro.c v1.0 FreeBSD 2013-05-17 00:02 stable $ */ 49 | /* 50 | * Make sure you have the latest release of UnQLite from: 51 | * http://unqlite.org/downloads.html 52 | */ 53 | #include /* puts() */ 54 | #include /* exit() */ 55 | /* Make sure this header file is available.*/ 56 | #include "unqlite.h" 57 | /* 58 | * Banner. 59 | */ 60 | static const char zBanner[] = { 61 | "============================================================\n" 62 | "UnQLite Cursors Intro \n" 63 | " http://unqlite.org/\n" 64 | "============================================================\n" 65 | }; 66 | /* 67 | * Extract the database error log and exit. 68 | */ 69 | static void Fatal(unqlite *pDb,const char *zMsg) 70 | { 71 | if( pDb ){ 72 | const char *zErr; 73 | int iLen = 0; /* Stupid cc warning */ 74 | 75 | /* Extract the database error log */ 76 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 77 | if( iLen > 0 ){ 78 | /* Output the DB error log */ 79 | puts(zErr); /* Always null terminated */ 80 | } 81 | }else{ 82 | if( zMsg ){ 83 | puts(zMsg); 84 | } 85 | } 86 | /* Manually shutdown the library */ 87 | unqlite_lib_shutdown(); 88 | /* Exit immediately */ 89 | exit(0); 90 | } 91 | /* Forward declaration: Data consumer callback */ 92 | static int DataConsumerCallback(const void *pData,unsigned int nDatalen,void *pUserData /* Unused */); 93 | /* 94 | * Maximum random records to be inserted in our database. 95 | */ 96 | #define MAX_RECORDS 20 97 | 98 | int main(int argc,char *argv[]) 99 | { 100 | unqlite *pDb; /* Database handle */ 101 | unqlite_kv_cursor *pCur; /* Cursor handle */ 102 | char zKey[14]; /* Random generated key */ 103 | char zData[32]; /* Dummy data */ 104 | int i,rc; 105 | 106 | puts(zBanner); 107 | 108 | /* Open our database */ 109 | rc = unqlite_open(&pDb,argc > 1 ? argv[1] /* On-disk DB */ : ":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 110 | if( rc != UNQLITE_OK ){ 111 | Fatal(0,"Out of memory"); 112 | } 113 | 114 | printf("Starting insertions of %d random records...\n",MAX_RECORDS); 115 | 116 | /* Start the random insertions */ 117 | for( i = 0 ; i < MAX_RECORDS; ++i ){ 118 | 119 | /* Genearte the random key first */ 120 | unqlite_util_random_string(pDb,zKey,sizeof(zKey)); 121 | 122 | /* Perform the insertion */ 123 | rc = unqlite_kv_store(pDb,zKey,sizeof(zKey),zData,sizeof(zData)); 124 | if( rc != UNQLITE_OK ){ 125 | /* Something goes wrong */ 126 | break; 127 | } 128 | } 129 | if( rc != UNQLITE_OK ){ 130 | /* Something goes wrong, extract the database error log and exit */ 131 | Fatal(pDb,0); 132 | } 133 | puts("Done...Starting the iteration process"); 134 | 135 | /* Allocate a new cursor instance */ 136 | rc = unqlite_kv_cursor_init(pDb,&pCur); 137 | if( rc != UNQLITE_OK ){ 138 | Fatal(0,"Out of memory"); 139 | } 140 | /* Point to the first record */ 141 | unqlite_kv_cursor_first_entry(pCur); 142 | /* To point to the last record instead of the first, simply call [unqlite_kv_cursor_last_entry()] as follows */ 143 | 144 | /* unqlite_kv_cursor_last_entry(pCur); */ 145 | 146 | /* Iterate over the entries */ 147 | while( unqlite_kv_cursor_valid_entry(pCur) ){ 148 | int nKeyLen; 149 | /* unqlite_int64 nDataLen; */ 150 | 151 | /* Consume the key */ 152 | unqlite_kv_cursor_key(pCur,0,&nKeyLen); /* Extract key length */ 153 | printf("\nKey ==> %u\n\t",nKeyLen); 154 | unqlite_kv_cursor_key_callback(pCur,DataConsumerCallback,0); 155 | 156 | /* Consume the data */ 157 | /* 158 | unqlite_kv_cursor_data(pCur,0,&nDataLen); 159 | printf("\nData ==> %lld\n\t",nDataLen); 160 | unqlite_kv_cursor_data_callback(pCur,DataConsumerCallback,0); 161 | */ 162 | 163 | 164 | /* Point to the next entry */ 165 | unqlite_kv_cursor_next_entry(pCur); 166 | 167 | /*unqlite_kv_cursor_prev_entry(pCur); //If [unqlite_kv_cursor_last_entry(pCur)] instead of [unqlite_kv_cursor_first_entry(pCur)] */ 168 | } 169 | /* Finally, Release our cursor */ 170 | unqlite_kv_cursor_release(pDb,pCur); 171 | 172 | /* Auto-commit the transaction and close our database */ 173 | unqlite_close(pDb); 174 | return 0; 175 | } 176 | 177 | #ifdef __WINNT__ 178 | #include 179 | #else 180 | /* Assume UNIX */ 181 | #include 182 | #endif 183 | /* 184 | * The following define is used by the UNIX build process and have 185 | * no particular meaning on windows. 186 | */ 187 | #ifndef STDOUT_FILENO 188 | #define STDOUT_FILENO 1 189 | #endif 190 | /* 191 | * Data consumer callback [unqlite_kv_fetch_callback(), unqlite_kv_cursor_key_callback(), etc.). 192 | * 193 | * Rather than allocating a static or dynamic buffer (Inefficient scenario for large data). 194 | * The caller simply need to supply a consumer callback which is responsible of consuming 195 | * the record data perhaps redirecting it (i.e. Record data) to its standard output (STDOUT), 196 | * disk file, connected peer and so forth. 197 | * Depending on how large the extracted data, the callback may be invoked more than once. 198 | */ 199 | static int DataConsumerCallback(const void *pData,unsigned int nDatalen,void *pUserData /* Unused */) 200 | { 201 | #ifdef __WINNT__ 202 | BOOL rc; 203 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pData,(DWORD)nDatalen,0,0); 204 | if( !rc ){ 205 | /* Abort processing */ 206 | return UNQLITE_ABORT; 207 | } 208 | #else 209 | ssize_t nWr; 210 | nWr = write(STDOUT_FILENO,pData,nDatalen); 211 | if( nWr < 0 ){ 212 | /* Abort processing */ 213 | return UNQLITE_ABORT; 214 | } 215 | #endif /* __WINT__ */ 216 | 217 | /* All done, data was redirected to STDOUT */ 218 | return UNQLITE_OK; 219 | } 220 | -------------------------------------------------------------------------------- /example/1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_kv_intro.c unqlite.c -o unqlite_kv 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Introduction to the Key/Value Store Interfaces: 12 | * 13 | * UnQLite is a standard key/value store similar to BerkeleyDB, Tokyo Cabinet, LevelDB, etc. 14 | * But, with a rich feature set including support for transactions (ACID), concurrent reader, etc. 15 | * Under the KV store, both keys and values are treated as simple arrays of bytes, so content 16 | * can be anything from ASCII strings, binary blob and even disk files. 17 | * The KV store layer is presented to host applications via a set of interfaces, these includes: 18 | * unqlite_kv_store(), unqlite_kv_append(), unqlite_kv_fetch_callback(), unqlite_kv_append_fmt(), 19 | * unqlite_kv_delete(), unqlite_kv_fetch(), etc. 20 | * 21 | * For an introduction to the UnQLite C/C++ interface, please refer to: 22 | * http://unqlite.org/api_intro.html 23 | * For the full C/C++ API reference guide, please refer to: 24 | * http://unqlite.org/c_api.html 25 | * UnQLite in 5 Minutes or Less: 26 | * http://unqlite.org/intro.html 27 | * The Architecture of the UnQLite Database Engine: 28 | * http://unqlite.org/arch.html 29 | * For an introduction to the UnQLite cursor interface, please refer to: 30 | * http://unqlite.org/c_api/unqlite_kv_cursor.html 31 | * For an introduction to Jx9 which is the scripting language which power 32 | * the Document-Store interface to UnQLite, please refer to: 33 | * http://unqlite.org/jx9.html 34 | */ 35 | /* $SymiscID: unqlite_kv_intro.c v1.0 FreeBSD 2013-05-14 10:17 stable $ */ 36 | /* 37 | * Make sure you have the latest release of UnQLite from: 38 | * http://unqlite.org/downloads.html 39 | */ 40 | #include /* puts() */ 41 | #include /* exit() */ 42 | /* Make sure this header file is available.*/ 43 | #include "unqlite.h" 44 | /* 45 | * Banner. 46 | */ 47 | static const char zBanner[] = { 48 | "============================================================\n" 49 | "UnQLite Key/Value Store Intro \n" 50 | " http://unqlite.org/\n" 51 | "============================================================\n" 52 | }; 53 | /* 54 | * Extract the database error log and exit. 55 | */ 56 | static void Fatal(unqlite *pDb,const char *zMsg) 57 | { 58 | if( pDb ){ 59 | const char *zErr; 60 | int iLen = 0; /* Stupid cc warning */ 61 | 62 | /* Extract the database error log */ 63 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 64 | if( iLen > 0 ){ 65 | /* Output the DB error log */ 66 | puts(zErr); /* Always null terminated */ 67 | } 68 | }else{ 69 | if( zMsg ){ 70 | puts(zMsg); 71 | } 72 | } 73 | /* Manually shutdown the library */ 74 | unqlite_lib_shutdown(); 75 | /* Exit immediately */ 76 | exit(0); 77 | } 78 | /* Forward declaration: Data consumer callback */ 79 | static int DataConsumerCallback(const void *pData,unsigned int nDatalen,void *pUserData /* Unused */); 80 | 81 | int main(int argc,char *argv[]) 82 | { 83 | unqlite *pDb; /* Database handle */ 84 | unqlite_kv_cursor *pCur; /* Cursor handle */ 85 | int i,rc; 86 | 87 | puts(zBanner); 88 | 89 | /* Open our database */ 90 | rc = unqlite_open(&pDb,argc > 1 ? argv[1] /* On-disk DB */ : ":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 91 | if( rc != UNQLITE_OK ){ 92 | Fatal(0,"Out of memory"); 93 | } 94 | 95 | /* Store some records */ 96 | rc = unqlite_kv_store(pDb,"test",-1,"Hello World",11); /* test => 'Hello World' */ 97 | if( rc != UNQLITE_OK ){ 98 | /* Insertion fail, extract database error log and exit */ 99 | Fatal(pDb,0); 100 | } 101 | /* A small formatted string */ 102 | rc = unqlite_kv_store_fmt(pDb,"date",-1,"dummy date: %d:%d:%d",2013,06,07); /* Dummy date */ 103 | if( rc != UNQLITE_OK ){ 104 | /* Insertion fail, extract database error log and exit */ 105 | Fatal(pDb,0); 106 | } 107 | 108 | /* Switch to the append interface */ 109 | rc = unqlite_kv_append(pDb,"msg",-1,"Hello, ",7); //msg => 'Hello, ' 110 | if( rc == UNQLITE_OK ){ 111 | /* The second chunk */ 112 | rc = unqlite_kv_append(pDb,"msg",-1,"dummy time is: ",17); /* msg => 'Hello, Current time is: '*/ 113 | if( rc == UNQLITE_OK ){ 114 | /* The last formatted chunk */ 115 | rc = unqlite_kv_append_fmt(pDb,"msg",-1,"%d:%d:%d",10,16,53); /* msg => 'Hello, Current time is: 10:16:53' */ 116 | } 117 | } 118 | /* Store 20 random records.*/ 119 | for(i = 0 ; i < 20 ; ++i ){ 120 | char zKey[12]; /* Random generated key */ 121 | char zData[34]; /* Dummy data */ 122 | 123 | /* Generate the random key */ 124 | unqlite_util_random_string(pDb,zKey,sizeof(zKey)); 125 | 126 | /* Perform the insertion */ 127 | rc = unqlite_kv_store(pDb,zKey,sizeof(zKey),zData,sizeof(zData)); 128 | if( rc != UNQLITE_OK ){ 129 | break; 130 | } 131 | } 132 | if( rc != UNQLITE_OK ){ 133 | /* Insertion fail, rollback the transaction */ 134 | rc = unqlite_rollback(pDb); 135 | if( rc != UNQLITE_OK ){ 136 | /* Extract database error log and exit */ 137 | Fatal(pDb,0); 138 | } 139 | } 140 | 141 | /* Delete a record */ 142 | unqlite_kv_delete(pDb,"test",-1); 143 | 144 | puts("Done...Starting the iteration process"); 145 | 146 | /* Allocate a new cursor instance */ 147 | rc = unqlite_kv_cursor_init(pDb,&pCur); 148 | if( rc != UNQLITE_OK ){ 149 | Fatal(0,"Out of memory"); 150 | } 151 | /* Point to the first record */ 152 | unqlite_kv_cursor_first_entry(pCur); 153 | 154 | 155 | /* Iterate over the entries */ 156 | while( unqlite_kv_cursor_valid_entry(pCur) ){ 157 | int nKeyLen; 158 | /*unqlite_int64 nDataLen;*/ 159 | 160 | /* Consume the key */ 161 | unqlite_kv_cursor_key(pCur,0,&nKeyLen); /* Extract key length */ 162 | printf("\nKey ==> %u\n\t",nKeyLen); 163 | unqlite_kv_cursor_key_callback(pCur,DataConsumerCallback,0); 164 | 165 | /* Consume the data */ 166 | 167 | /* 168 | unqlite_kv_cursor_data(pCur,0,&nDataLen); 169 | printf("\nData ==> %lld\n\t",nDataLen); 170 | unqlite_kv_cursor_data_callback(pCur,DataConsumerCallback,0); 171 | */ 172 | 173 | /* Point to the next entry */ 174 | unqlite_kv_cursor_next_entry(pCur); 175 | 176 | } 177 | /* Finally, Release our cursor */ 178 | unqlite_kv_cursor_release(pDb,pCur); 179 | 180 | /* Auto-commit the transaction and close our database */ 181 | unqlite_close(pDb); 182 | return 0; 183 | } 184 | 185 | #ifdef __WINNT__ 186 | #include 187 | #else 188 | /* Assume UNIX */ 189 | #include 190 | #endif 191 | /* 192 | * The following define is used by the UNIX build process and have 193 | * no particular meaning on windows. 194 | */ 195 | #ifndef STDOUT_FILENO 196 | #define STDOUT_FILENO 1 197 | #endif 198 | /* 199 | * Data consumer callback [unqlite_kv_fetch_callback(), unqlite_kv_cursor_key_callback(), etc.). 200 | * 201 | * Rather than allocating a static or dynamic buffer (Inefficient scenario for large data). 202 | * The caller simply need to supply a consumer callback which is responsible of consuming 203 | * the record data perhaps redirecting it (i.e. Record data) to its standard output (STDOUT), 204 | * disk file, connected peer and so forth. 205 | * Depending on how large the extracted data, the callback may be invoked more than once. 206 | */ 207 | static int DataConsumerCallback(const void *pData,unsigned int nDatalen,void *pUserData /* Unused */) 208 | { 209 | #ifdef __WINNT__ 210 | BOOL rc; 211 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pData,(DWORD)nDatalen,0,0); 212 | if( !rc ){ 213 | /* Abort processing */ 214 | return UNQLITE_ABORT; 215 | } 216 | #else 217 | ssize_t nWr; 218 | nWr = write(STDOUT_FILENO,pData,nDatalen); 219 | if( nWr < 0 ){ 220 | /* Abort processing */ 221 | return UNQLITE_ABORT; 222 | } 223 | #endif /* __WINT__ */ 224 | 225 | /* All done, data was redirected to STDOUT */ 226 | return UNQLITE_OK; 227 | } 228 | -------------------------------------------------------------------------------- /example/unqlite_huge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_huge_insert.c unqlite.c -o unqlite_huge 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * This program stores over 100000 random records (Dummy data of length 32 + random key of length 14) 12 | * in the given database. The random keys are obtained using 13 | * the powerful [unqlite_util_random_string()] interface. 14 | * Feel free to raise this number to 1 million or whatever value 15 | * you want and do your own benchmark. 16 | * Note that if you generate 1 million records, you'll end up 17 | * with a 560 MB database file with garbage data. 18 | * Only Key/Value store interfaces (unqlite_kv_store()) are used 19 | * in this example. 20 | * 21 | * Typical usage of this program: 22 | * 23 | * ./unqlite_huge test.db 24 | * 25 | * To iterate (using the cursor interfaces) over the inserted records, use the '-i' command 26 | * as follows: 27 | * 28 | * ./unqlite_huge test.db -i 29 | * 30 | * To start an in-memory database, invoke the program without arguments as follows: 31 | * 32 | * ./unqlite_huge 33 | * 34 | * For an introduction to the UnQLite C/C++ interface, please refer to: 35 | * http://unqlite.org/api_intro.html 36 | * For the full C/C++ API reference guide, please refer to: 37 | * http://unqlite.org/c_api.html 38 | * UnQLite in 5 Minutes or Less: 39 | * http://unqlite.org/intro.html 40 | * The Architecture of the UnQLite Database Engine: 41 | * http://unqlite.org/arch.html 42 | * For an introduction to Jx9 which is the scripting language which power 43 | * the Document-Store interface to UnQLite, please refer to: 44 | * http://unqlite.org/jx9.html 45 | */ 46 | /* $SymiscID: unqlite_huge_insert.c v1.0 Solaris 2013-05-15 20:17 stable $ */ 47 | /* 48 | * Make sure you have the latest release of UnQLite from: 49 | * http://unqlite.org/downloads.html 50 | */ 51 | #include /* puts() */ 52 | #include /* exit() */ 53 | /* Make sure this header file is available.*/ 54 | #include "unqlite.h" 55 | /* 56 | * Banner. 57 | */ 58 | static const char zBanner[] = { 59 | "============================================================\n" 60 | "UnQLite Huge Random Insertions \n" 61 | " http://unqlite.org/\n" 62 | "============================================================\n" 63 | }; 64 | /* 65 | * Extract the database error log and exit. 66 | */ 67 | static void Fatal(unqlite *pDb, const char *zMsg) 68 | { 69 | if (pDb) { 70 | const char *zErr; 71 | int iLen = 0; /* Stupid cc warning */ 72 | 73 | /* Extract the database error log */ 74 | unqlite_config(pDb, UNQLITE_CONFIG_ERR_LOG, &zErr, &iLen); 75 | if (iLen > 0) { 76 | /* Output the DB error log */ 77 | puts(zErr); /* Always null terminated */ 78 | } 79 | } 80 | else { 81 | if (zMsg) { 82 | puts(zMsg); 83 | } 84 | } 85 | /* Manually shutdown the library */ 86 | unqlite_lib_shutdown(); 87 | /* Exit immediately */ 88 | exit(0); 89 | } 90 | /* Forward declaration: Data consumer callback */ 91 | static int DataConsumerCallback(const void *pData, unsigned int nDatalen, void *pUserData /* Unused */); 92 | /* 93 | * Maximum records to be inserted in our database. 94 | */ 95 | #define MAX_RECORDS 100000 96 | 97 | int main(int argc, char *argv[]) 98 | { 99 | const char *zPath = ":mem:"; /* Assume an in-memory database */ 100 | int db_iterate = 0; /* TRUE to iterate over the inserted elements */ 101 | unqlite *pDb; /* Database handle */ 102 | char zKey[14]; /* Random generated key */ 103 | char zData[32]; /* Dummy data */ 104 | int i, rc; 105 | 106 | /* Process arguments */ 107 | for (i = 1; i < argc; ++i) { 108 | int c; 109 | if (argv[i][0] != '-') { 110 | /* Database file */ 111 | zPath = argv[i]; 112 | continue; 113 | } 114 | c = argv[i][1]; 115 | if (c == 'i' || c == 'I') { 116 | /* Iterate over the inserted elements */ 117 | db_iterate = 1; 118 | } 119 | } 120 | puts(zBanner); 121 | 122 | /* Open our database */ 123 | rc = unqlite_open(&pDb, zPath, UNQLITE_OPEN_CREATE); 124 | if (rc != UNQLITE_OK) { 125 | Fatal(0, "Out of memory"); 126 | } 127 | 128 | printf("Starting insertions of %d random records...\n", MAX_RECORDS); 129 | 130 | /* Start the random insertions */ 131 | for (i = 0; i < MAX_RECORDS; ++i) { 132 | 133 | /* Generate the random key first */ 134 | unqlite_util_random_string(pDb, zKey, sizeof(zKey)); 135 | 136 | /* Perform the insertion */ 137 | rc = unqlite_kv_store(pDb, zKey, sizeof(zKey), zData, sizeof(zData)); 138 | if (rc != UNQLITE_OK) { 139 | /* Something goes wrong */ 140 | break; 141 | } 142 | 143 | if (i == 79125) { 144 | /* Insert a sentinel record */ 145 | 146 | /* time(&tt); pTm = localtime(&tt); ... */ 147 | unqlite_kv_store_fmt(pDb, "sentinel", -1, "I'm a sentinel record inserted on %d:%d:%d\n", 14, 15, 18); /* Dummy time */ 148 | } 149 | } 150 | 151 | /* If we are OK, then manually commit the transaction */ 152 | if (rc == UNQLITE_OK) { 153 | /* 154 | * In fact, a call to unqlite_commit() is not necessary since UnQLite 155 | * will automatically commit the transaction during a call to unqlite_close(). 156 | */ 157 | rc = unqlite_commit(pDb); 158 | if (rc != UNQLITE_OK) { 159 | /* Rollback the transaction */ 160 | rc = unqlite_rollback(pDb); 161 | } 162 | } 163 | 164 | if (rc != UNQLITE_OK) { 165 | /* Something goes wrong, extract the database error log and exit */ 166 | Fatal(pDb, 0); 167 | } 168 | puts("Done...Fetching the 'sentinel' record: "); 169 | 170 | /* Fetch the sentinel */ 171 | rc = unqlite_kv_fetch_callback(pDb, "sentinel", -1, DataConsumerCallback, 0); 172 | if (rc != UNQLITE_OK) { 173 | /* Can't happen */ 174 | Fatal(0, "Sentinel record not found"); 175 | } 176 | 177 | if (db_iterate) { 178 | /* Iterate over the inserted records */ 179 | unqlite_kv_cursor *pCur; 180 | 181 | /* Allocate a new cursor instance */ 182 | rc = unqlite_kv_cursor_init(pDb, &pCur); 183 | if (rc != UNQLITE_OK) { 184 | Fatal(0, "Out of memory"); 185 | } 186 | 187 | /* Point to the first record */ 188 | unqlite_kv_cursor_first_entry(pCur); 189 | 190 | /* Iterate over the entries */ 191 | while (unqlite_kv_cursor_valid_entry(pCur)) { 192 | int nKeyLen; 193 | /* unqlite_int64 nDataLen; */ 194 | 195 | /* Consume the key */ 196 | unqlite_kv_cursor_key(pCur, 0, &nKeyLen); /* Extract key length */ 197 | printf("\nKey ==> %u\n\t", nKeyLen); 198 | unqlite_kv_cursor_key_callback(pCur, DataConsumerCallback, 0); 199 | 200 | /* Consume the data */ 201 | /* 202 | unqlite_kv_cursor_data(pCur,0,&nDataLen); 203 | printf("\nData ==> %lld\n\t",nDataLen); 204 | unqlite_kv_cursor_data_callback(pCur,DataConsumerCallback,0); 205 | */ 206 | 207 | 208 | /* Point to the next entry */ 209 | unqlite_kv_cursor_next_entry(pCur); 210 | } 211 | 212 | /* Finally, Release our cursor */ 213 | unqlite_kv_cursor_release(pDb, pCur); 214 | } 215 | 216 | /* All done, close our database */ 217 | unqlite_close(pDb); 218 | return 0; 219 | } 220 | 221 | #ifdef __WINNT__ 222 | #include 223 | #else 224 | /* Assume UNIX */ 225 | #include 226 | #endif 227 | /* 228 | * The following define is used by the UNIX build process and have 229 | * no particular meaning on windows. 230 | */ 231 | #ifndef STDOUT_FILENO 232 | #define STDOUT_FILENO 1 233 | #endif 234 | /* 235 | * Data consumer callback [unqlite_kv_fetch_callback(), unqlite_kv_cursor_key_callback(), etc.). 236 | * 237 | * Rather than allocating a static or dynamic buffer (Inefficient scenario for large data). 238 | * The caller simply need to supply a consumer callback which is responsible of consuming 239 | * the record data perhaps redirecting it (i.e. Record data) to its standard output (STDOUT), 240 | * disk file, connected peer and so forth. 241 | * Depending on how large the extracted data, the callback may be invoked more than once. 242 | */ 243 | static int DataConsumerCallback(const void *pData, unsigned int nDatalen, void *pUserData /* Unused */) 244 | { 245 | #ifdef __WINNT__ 246 | BOOL rc; 247 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), pData, (DWORD)nDatalen, 0, 0); 248 | if (!rc) { 249 | /* Abort processing */ 250 | return UNQLITE_ABORT; 251 | } 252 | #else 253 | ssize_t nWr; 254 | nWr = write(STDOUT_FILENO, pData, nDatalen); 255 | if (nWr < 0) { 256 | /* Abort processing */ 257 | return UNQLITE_ABORT; 258 | } 259 | #endif /* __WINT__ */ 260 | /* All done, data was redirected to STDOUT */ 261 | return UNQLITE_OK; 262 | } 263 | -------------------------------------------------------------------------------- /example/unqlite_tar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_tar.c unqlite.c -o unqlite_tar 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Turn a UnQLite database into a TAR-like archive with O(1) record lookup. 12 | * 13 | * Typical usage of this program: 14 | * 15 | * To store files in the database, simply invoke the program with the '-w' command as follows: 16 | * 17 | * ./unqlite_tar test.db -w file1 file2 file3... 18 | * 19 | * To extract a stored file, use the '-r' as follows: 20 | * 21 | * ./unqlite_tar test.db -r file1 file2... 22 | * 23 | * To iterate (using the cursor interfaces) over the inserted records, use the '-i' command 24 | * as follows: 25 | * 26 | * ./unqlite_tar test.db -i 27 | * 28 | * Only Key/Value store plus some utility interfaces are used in this example. 29 | * 30 | * For an introduction to the UnQLite C/C++ interface, please refer to: 31 | * http://unqlite.org/api_intro.html 32 | * For the full C/C++ API reference guide, please refer to: 33 | * http://unqlite.org/c_api.html 34 | * UnQLite in 5 Minutes or Less: 35 | * http://unqlite.org/intro.html 36 | * The Architecture of the UnQLite Database Engine: 37 | * http://unqlite.org/arch.html 38 | * For an introduction to Jx9 which is the scripting language which power 39 | * the Document-Store interface to UnQLite, please refer to: 40 | * http://unqlite.org/jx9.html 41 | */ 42 | /* $SymiscID: unqlite_huge_insert.c v1.0 Solaris 2013-05-15 20:17 stable $ */ 43 | /* 44 | * Make sure you have the latest release of UnQLite from: 45 | * http://unqlite.org/downloads.html 46 | */ 47 | #include /* puts() */ 48 | #include /* exit() */ 49 | 50 | #if defined(_MSC_VER) || defined(__MINGW32__) 51 | #define NUM_FORMAT "l" 52 | #else 53 | #define NUM_FORMAT "" 54 | #endif 55 | 56 | /* Make sure this header file is available.*/ 57 | #include "unqlite.h" 58 | /* 59 | * Banner. 60 | */ 61 | static const char zBanner[] = { 62 | "============================================================\n" 63 | "UnQLite TAR \n" 64 | " http://unqlite.org/\n" 65 | "============================================================\n" 66 | }; 67 | /* 68 | * Display the banner, a help message and exit. 69 | */ 70 | static void Help(void) 71 | { 72 | puts(zBanner); 73 | puts("unqlite_tar db_name (-r|-w|-i) file1 [file2 ...]"); 74 | puts("\t-w: Store one or more files in the database"); 75 | puts("\t-r: Extract records from the database"); 76 | puts("\t-i: Iterate over the stored files"); 77 | /* Exit immediately */ 78 | exit(0); 79 | } 80 | /* 81 | * Extract the database error log and exit. 82 | */ 83 | static void Fatal(unqlite *pDb, const char *zMsg) 84 | { 85 | if (pDb) { 86 | const char *zErr; 87 | int iLen = 0; /* Stupid cc warning */ 88 | 89 | /* Extract the database error log */ 90 | unqlite_config(pDb, UNQLITE_CONFIG_ERR_LOG, &zErr, &iLen); 91 | if (iLen > 0) { 92 | /* Output the DB error log */ 93 | puts(zErr); /* Always null terminated */ 94 | } 95 | } 96 | else { 97 | if (zMsg) { 98 | puts(zMsg); 99 | } 100 | } 101 | /* Manually shutdown the library */ 102 | unqlite_lib_shutdown(); 103 | /* Exit immediately */ 104 | exit(0); 105 | } 106 | /* Forward declaration: Data consumer callback */ 107 | static int DataConsumerCallback(const void *pData, unsigned int nDatalen, void *pUserData /* Unused */); 108 | 109 | 110 | int main(int argc, char *argv[]) 111 | { 112 | int db_extract = 0; /* TRUE to extract records from the dabase */ 113 | int db_store = 0; /* TRUE to store files in the database */ 114 | int db_iterate = 0; /* TRUE to iterate over the inserted elements */ 115 | unqlite *pDb; /* Database handle */ 116 | int c, i, rc; 117 | 118 | if (argc < 3) { 119 | /* Missing database name */ 120 | Help(); 121 | } 122 | 123 | c = argv[2][0]; 124 | if (c != '-') { 125 | /* Missing command */ 126 | Help(); 127 | } 128 | c = argv[2][1]; 129 | if (c == 'i' || c == 'I') { 130 | /* Iterate over the inserted elements */ 131 | db_iterate = 1; 132 | } 133 | else if (c == 'w' || c == 'W') { 134 | /* Store some files */ 135 | db_store = 1; 136 | } 137 | else { 138 | /* Extract some records */ 139 | db_extract = 1; 140 | } 141 | 142 | 143 | /* Open our database */ 144 | rc = unqlite_open(&pDb, argv[1], db_store ? UNQLITE_OPEN_CREATE : (UNQLITE_OPEN_READONLY | UNQLITE_OPEN_MMAP) /* Read-only DB */); 145 | if (rc != UNQLITE_OK) { 146 | Fatal(0, "Out of memory"); 147 | } 148 | 149 | if (db_store) { 150 | void *pMap; /* Read-only memory view of the target file */ 151 | unqlite_int64 nSize; /* file size */ 152 | 153 | /* Start the insertion */ 154 | for (i = 3; i < argc; ++i) { 155 | const char *zFile = argv[i]; 156 | printf("Inserting %s\t ... ", zFile); 157 | 158 | /* Obtain a read-only memory view of the whole file */ 159 | rc = unqlite_util_load_mmaped_file(zFile, &pMap, &nSize); 160 | if (rc == UNQLITE_OK) { 161 | /* Store the whole file */ 162 | rc = unqlite_kv_store(pDb, zFile, -1, pMap, nSize); 163 | /* Discard this view */ 164 | unqlite_util_release_mmaped_file(pMap, nSize); 165 | } 166 | puts(rc == UNQLITE_OK ? "OK" : "Fail"); 167 | } 168 | /* Manually commit the transaction. 169 | * In fact, a call to unqlite_commit() is not necessary since UnQLite 170 | * will automatically commit the transaction during a call to unqlite_close(). 171 | */ 172 | rc = unqlite_commit(pDb); 173 | if (rc != UNQLITE_OK) { 174 | /* Rollback the transaction */ 175 | rc = unqlite_rollback(pDb); 176 | } 177 | if (rc != UNQLITE_OK) { 178 | /* Something goes wrong, extract the database error log and exit */ 179 | Fatal(pDb, 0); 180 | } 181 | } 182 | else if (db_iterate) { 183 | /* Iterate over the inserted records */ 184 | unqlite_kv_cursor *pCur; 185 | 186 | /* Allocate a new cursor instance */ 187 | rc = unqlite_kv_cursor_init(pDb, &pCur); 188 | if (rc != UNQLITE_OK) { 189 | Fatal(0, "Out of memory"); 190 | } 191 | 192 | /* Point to the first record */ 193 | unqlite_kv_cursor_first_entry(pCur); 194 | 195 | /* Iterate over the entries */ 196 | while (unqlite_kv_cursor_valid_entry(pCur)) { 197 | unqlite_int64 nDataLen; 198 | 199 | /* Consume the key */ 200 | unqlite_kv_cursor_key_callback(pCur, DataConsumerCallback, 0); 201 | 202 | /* Extract the data size */ 203 | unqlite_kv_cursor_data(pCur, 0, &nDataLen); 204 | printf(":\t %l"NUM_FORMAT"d Bytes\n", nDataLen); 205 | /* unqlite_kv_cursor_data_callback(pCur,DataConsumerCallback,0); */ 206 | 207 | /* Point to the next entry */ 208 | unqlite_kv_cursor_next_entry(pCur); 209 | } 210 | /* Finally, Release our cursor */ 211 | unqlite_kv_cursor_release(pDb, pCur); 212 | } 213 | else { 214 | /* Extract one more records */ 215 | for (i = 3; i < argc; ++i) { 216 | const char *zFile = argv[i]; 217 | rc = unqlite_kv_fetch_callback(pDb, zFile, -1, DataConsumerCallback, 0); 218 | if (rc == UNQLITE_NOTFOUND) { 219 | printf("No such record: %s\n", zFile); 220 | } 221 | } 222 | } 223 | 224 | /* All done, close our database */ 225 | unqlite_close(pDb); 226 | return 0; 227 | } 228 | 229 | #ifdef __WINNT__ 230 | #include 231 | #else 232 | /* Assume UNIX */ 233 | #include 234 | #endif 235 | /* 236 | * The following define is used by the UNIX build process and have 237 | * no particular meaning on windows. 238 | */ 239 | #ifndef STDOUT_FILENO 240 | #define STDOUT_FILENO 1 241 | #endif 242 | /* 243 | * Data consumer callback [unqlite_kv_fetch_callback(), unqlite_kv_cursor_key_callback(), etc.). 244 | * 245 | * Rather than allocating a static or dynamic buffer (Inefficient scenario for large data). 246 | * The caller simply need to supply a consumer callback which is responsible of consuming 247 | * the record data perhaps redirecting it (i.e. Record data) to its standard output (STDOUT), 248 | * disk file, connected peer and so forth. 249 | * Depending on how large the extracted data, the callback may be invoked more than once. 250 | */ 251 | static int DataConsumerCallback(const void *pData, unsigned int nDatalen, void *pUserData /* Unused */) 252 | { 253 | #ifdef __WINNT__ 254 | BOOL rc; 255 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), pData, (DWORD)nDatalen, 0, 0); 256 | if (!rc) { 257 | /* Abort processing */ 258 | return UNQLITE_ABORT; 259 | } 260 | #else 261 | ssize_t nWr; 262 | nWr = write(STDOUT_FILENO, pData, nDatalen); 263 | if (nWr < 0) { 264 | /* Abort processing */ 265 | return UNQLITE_ABORT; 266 | } 267 | #endif /* __WINT__ */ 268 | /* All done, data was redirected to STDOUT */ 269 | return UNQLITE_OK; 270 | } 271 | -------------------------------------------------------------------------------- /example/4.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_doc_intro.c unqlite.c -o unqlite_doc 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Introduction to the UnQLite Document-Store Interfaces: 12 | * 13 | * The Document store to UnQLite which is used to store JSON docs (i.e. Objects, Arrays, Strings, etc.) 14 | * in the database is powered by the Jx9 programming language. 15 | * 16 | * Jx9 is an embeddable scripting language also called extension language designed 17 | * to support general procedural programming with data description facilities. 18 | * Jx9 is a Turing-Complete, dynamically typed programming language based on JSON 19 | * and implemented as a library in the UnQLite core. 20 | * 21 | * Jx9 is built with a tons of features and has a clean and familiar syntax similar 22 | * to C and Javascript. 23 | * Being an extension language, Jx9 has no notion of a main program, it only works 24 | * embedded in a host application. 25 | * The host program (UnQLite in our case) can write and read Jx9 variables and can 26 | * register C/C++ functions to be called by Jx9 code. 27 | * 28 | * For an introduction to the UnQLite C/C++ interface, please refer to: 29 | * http://unqlite.org/api_intro.html 30 | * For an introduction to Jx9, please refer to: 31 | * http://unqlite.org/jx9.html 32 | * For the full C/C++ API reference guide, please refer to: 33 | * http://unqlite.org/c_api.html 34 | * UnQLite in 5 Minutes or Less: 35 | * http://unqlite.org/intro.html 36 | * The Architecture of the UnQLite Database Engine: 37 | * http://unqlite.org/arch.html 38 | */ 39 | /* $SymiscID: unqlite_doc_intro.c v1.0 FreeBSD 2013-05-17 15:56 stable $ */ 40 | /* 41 | * Make sure you have the latest release of UnQLite from: 42 | * http://unqlite.org/downloads.html 43 | */ 44 | #include /* puts() */ 45 | #include /* exit() */ 46 | /* Make sure this header file is available.*/ 47 | #include "unqlite.h" 48 | /* 49 | * Banner. 50 | */ 51 | static const char zBanner[] = { 52 | "============================================================\n" 53 | "UnQLite Document-Store (Via Jx9) Intro \n" 54 | " http://unqlite.org/\n" 55 | "============================================================\n" 56 | }; 57 | /* 58 | * Extract the database error log and exit. 59 | */ 60 | static void Fatal(unqlite *pDb,const char *zMsg) 61 | { 62 | if( pDb ){ 63 | const char *zErr; 64 | int iLen = 0; /* Stupid cc warning */ 65 | 66 | /* Extract the database error log */ 67 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 68 | if( iLen > 0 ){ 69 | /* Output the DB error log */ 70 | puts(zErr); /* Always null terminated */ 71 | } 72 | }else{ 73 | if( zMsg ){ 74 | puts(zMsg); 75 | } 76 | } 77 | /* Manually shutdown the library */ 78 | unqlite_lib_shutdown(); 79 | /* Exit immediately */ 80 | exit(0); 81 | } 82 | /* Forward declaration: VM output consumer callback */ 83 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */); 84 | /* 85 | * The following is the Jx9 Program to be executed later by the UnQLite VM: 86 | * This program store some JSON objects (a collections of dummy users) into 87 | * the collection 'users' stored in our database. 88 | * // Create the collection 'users' 89 | * if( !db_exists('users') ){ 90 | * // Try to create it 91 | * $rc = db_create('users'); 92 | * if ( !$rc ){ 93 | * //Handle error 94 | * print db_errlog(); 95 | * return; 96 | * } 97 | * } 98 | * //The following is the records to be stored shortly in our collection 99 | * $zRec = [ 100 | * { 101 | * name : 'james', 102 | * age : 27, 103 | * mail : 'dude@example.com' 104 | * }, 105 | * { 106 | * name : 'robert', 107 | * age : 35, 108 | * mail : 'rob@example.com' 109 | * }, 110 | * 111 | * { 112 | * name : 'monji', 113 | * age : 47, 114 | * mail : 'monji@example.com' 115 | * }, 116 | * { 117 | * name : 'barzini', 118 | * age : 52, 119 | * mail : 'barz@mobster.com' 120 | * } 121 | * ]; 122 | * 123 | * //Store our records 124 | * $rc = db_store('users',$zRec); 125 | * if( !$rc ){ 126 | * //Handle error 127 | * print db_errlog(); 128 | * return; 129 | * } 130 | * //Create our filter callback 131 | * $zCallback = function($rec){ 132 | * //Allow only users >= 30 years old. 133 | * if( $rec.age < 30 ){ 134 | * // Discard this record 135 | * return FALSE; 136 | * } 137 | * //Record correspond to our criteria 138 | * return TRUE; 139 | * }; //Don't forget the semi-colon here 140 | * 141 | * //Retrieve collection records and apply our filter callback 142 | * $data = db_fetch_all('users',$zCallback); 143 | * 144 | * //Iterate over the extracted elements 145 | * foreach($data as $value){ //JSON array holding the filtered records 146 | * print $value..JX9_EOL; 147 | * } 148 | */ 149 | #define JX9_PROG \ 150 | "/* Create the collection 'users' */"\ 151 | "if( !db_exists('users') ){"\ 152 | " /* Try to create it */"\ 153 | " $rc = db_create('users');"\ 154 | " if ( !$rc ){"\ 155 | " /*Handle error*/"\ 156 | " print db_errlog();"\ 157 | " return;"\ 158 | " }else{"\ 159 | " print \"Collection 'users' successfuly created\\n\";"\ 160 | " }"\ 161 | " }"\ 162 | "/*The following is the records to be stored shortly in our collection*/ "\ 163 | "$zRec = ["\ 164 | "{"\ 165 | " name : 'james',"\ 166 | " age : 27,"\ 167 | " mail : 'dude@example.com'"\ 168 | "},"\ 169 | "{"\ 170 | " name : 'robert',"\ 171 | " age : 35,"\ 172 | " mail : 'rob@example.com'"\ 173 | "},"\ 174 | "{"\ 175 | " name : 'monji',"\ 176 | " age : 47,"\ 177 | " mail : 'monji@example.com'"\ 178 | "},"\ 179 | "{"\ 180 | " name : 'barzini',"\ 181 | " age : 52,"\ 182 | " mail : 'barz@mobster.com'"\ 183 | "}"\ 184 | "];"\ 185 | "/*Store our records*/"\ 186 | "$rc = db_store('users',$zRec);"\ 187 | "if( !$rc ){"\ 188 | " /*Handle error*/"\ 189 | " print db_errlog();"\ 190 | " return;"\ 191 | "}"\ 192 | "/*Create our filter callback*/"\ 193 | "$zCallback = function($rec){"\ 194 | " /*Allow only users >= 30 years old.*/"\ 195 | " if( $rec.age < 30 ){"\ 196 | " /* Discard this record*/"\ 197 | " return FALSE;"\ 198 | " }"\ 199 | " /* Record correspond to our criteria*/"\ 200 | " return TRUE;"\ 201 | "}; /* Don't forget the semi-colon here*/"\ 202 | "/* Retrieve collection records and apply our filter callback*/"\ 203 | "$data = db_fetch_all('users',$zCallback);"\ 204 | "print \"Filtered records\\n\";"\ 205 | "/*Iterate over the extracted elements*/"\ 206 | "foreach($data as $value){ /*JSON array holding the filtered records*/"\ 207 | " print $value..JX9_EOL;"\ 208 | "}" 209 | 210 | int main(int argc,char *argv[]) 211 | { 212 | unqlite *pDb; /* Database handle */ 213 | unqlite_vm *pVm; /* UnQLite VM resulting from successful compilation of the target Jx9 script */ 214 | int rc; 215 | 216 | puts(zBanner); 217 | 218 | /* Open our database */ 219 | rc = unqlite_open(&pDb,argc > 1 ? argv[1] /* On-disk DB */ : ":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 220 | if( rc != UNQLITE_OK ){ 221 | Fatal(0,"Out of memory"); 222 | } 223 | 224 | /* Compile our Jx9 script defined above */ 225 | rc = unqlite_compile(pDb,JX9_PROG,sizeof(JX9_PROG)-1,&pVm); 226 | if( rc != UNQLITE_OK ){ 227 | /* Compile error, extract the compiler error log */ 228 | const char *zBuf; 229 | int iLen; 230 | /* Extract error log */ 231 | unqlite_config(pDb,UNQLITE_CONFIG_JX9_ERR_LOG,&zBuf,&iLen); 232 | if( iLen > 0 ){ 233 | puts(zBuf); 234 | } 235 | Fatal(0,"Jx9 compile error"); 236 | } 237 | 238 | /* Install a VM output consumer callback */ 239 | rc = unqlite_vm_config(pVm,UNQLITE_VM_CONFIG_OUTPUT,VmOutputConsumer,0); 240 | if( rc != UNQLITE_OK ){ 241 | Fatal(pDb,0); 242 | } 243 | 244 | /* Execute our script */ 245 | rc = unqlite_vm_exec(pVm); 246 | if( rc != UNQLITE_OK ){ 247 | Fatal(pDb,0); 248 | } 249 | 250 | /* Release our VM */ 251 | unqlite_vm_release(pVm); 252 | 253 | /* Auto-commit the transaction and close our database */ 254 | unqlite_close(pDb); 255 | return 0; 256 | } 257 | 258 | #ifdef __WINNT__ 259 | #include 260 | #else 261 | /* Assume UNIX */ 262 | #include 263 | #endif 264 | /* 265 | * The following define is used by the UNIX build process and have 266 | * no particular meaning on windows. 267 | */ 268 | #ifndef STDOUT_FILENO 269 | #define STDOUT_FILENO 1 270 | #endif 271 | /* 272 | * VM output consumer callback. 273 | * Each time the UnQLite VM generates some outputs, the following 274 | * function gets called by the underlying virtual machine to consume 275 | * the generated output. 276 | * 277 | * All this function does is redirecting the VM output to STDOUT. 278 | * This function is registered via a call to [unqlite_vm_config()] 279 | * with a configuration verb set to: UNQLITE_VM_CONFIG_OUTPUT. 280 | */ 281 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */) 282 | { 283 | #ifdef __WINNT__ 284 | BOOL rc; 285 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pOutput,(DWORD)nOutLen,0,0); 286 | if( !rc ){ 287 | /* Abort processing */ 288 | return UNQLITE_ABORT; 289 | } 290 | #else 291 | ssize_t nWr; 292 | nWr = write(STDOUT_FILENO,pOutput,nOutLen); 293 | if( nWr < 0 ){ 294 | /* Abort processing */ 295 | return UNQLITE_ABORT; 296 | } 297 | #endif /* __WINT__ */ 298 | 299 | /* All done, data was redirected to STDOUT */ 300 | return UNQLITE_OK; 301 | } 302 | -------------------------------------------------------------------------------- /example/2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_const_intro.c unqlite.c -o unqlite_jx9_const 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Introduction to the UnQLite (Via Jx9) Constant Expansion Mechanism: 12 | * 13 | * The Document store to UnQLite which is used to store JSON docs (i.e. Objects, Arrays, Strings, etc.) 14 | * in the database is powered by the Jx9 programming language. 15 | * 16 | * Jx9 is an embeddable scripting language also called extension language designed 17 | * to support general procedural programming with data description facilities. 18 | * Jx9 is a Turing-Complete, dynamically typed programming language based on JSON 19 | * and implemented as a library in the UnQLite core. 20 | * 21 | * Jx9 is built with a tons of features and has a clean and familiar syntax similar 22 | * to C and Javascript. 23 | * Being an extension language, Jx9 has no notion of a main program, it only works 24 | * embedded in a host application. 25 | * The host program (UnQLite in our case) can write and read Jx9 variables and can 26 | * register C/C++ functions to be called by Jx9 code. 27 | * 28 | * The constant expansion mechanism under Jx9 is extremely powerful yet simple and work 29 | * as follows: 30 | * Each registered constant have a C procedure associated with it. This procedure known 31 | * as the constant expansion callback is responsible of expanding the invoked constant 32 | * to the desired value, for example: 33 | * The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI). 34 | * the "__OS__" constant procedure expands to the name of the host Operating 35 | * Systems (Windows, Linux, ...), the "__TIME__" constant expands to the current system time 36 | * and so forth. 37 | * 38 | * For an introductory course to the constant expansion meachanism, you can refer 39 | * to the following tutorial: 40 | * http://unqlite.org/const_intro.html 41 | * For an introduction to the UnQLite C/C++ interface, please refer to: 42 | * http://unqlite.org/api_intro.html 43 | * For an introduction to Jx9, please refer to: 44 | * http://unqlite.org/jx9.html 45 | * For the full C/C++ API reference guide, please refer to: 46 | * http://unqlite.org/c_api.html 47 | * UnQLite in 5 Minutes or Less: 48 | * http://unqlite.org/intro.html 49 | * The Architecture of the UnQLite Database Engine: 50 | * http://unqlite.org/arch.html 51 | */ 52 | /* $SymiscID: unqlite_const_intro.c v1.5 Unix 2013-05-17 00:17 stable $ */ 53 | /* 54 | * Make sure you have the latest release of UnQLite from: 55 | * http://unqlite.org/downloads.html 56 | */ 57 | #include /* puts() */ 58 | #include /* exit() */ 59 | /* Make sure this header file is available.*/ 60 | #include "unqlite.h" 61 | /* 62 | * Banner. 63 | */ 64 | static const char zBanner[] = { 65 | "============================================================\n" 66 | "UnQLite (Via Jx9) Constant Expansion Mechanism \n" 67 | " http://unqlite.org/\n" 68 | "============================================================\n" 69 | }; 70 | /* 71 | * Extract the database error log and exit. 72 | */ 73 | static void Fatal(unqlite *pDb,const char *zMsg) 74 | { 75 | if( pDb ){ 76 | const char *zErr; 77 | int iLen = 0; /* Stupid cc warning */ 78 | 79 | /* Extract the database error log */ 80 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 81 | if( iLen > 0 ){ 82 | /* Output the DB error log */ 83 | puts(zErr); /* Always null terminated */ 84 | } 85 | }else{ 86 | if( zMsg ){ 87 | puts(zMsg); 88 | } 89 | } 90 | /* Manually shutdown the library */ 91 | unqlite_lib_shutdown(); 92 | /* Exit immediately */ 93 | exit(0); 94 | } 95 | /* 96 | * __PI__: expand the value of PI (3.14...) 97 | * Our first constant is the __PI__ constant. The following procedure 98 | * is the callback associated with the __PI__ identifier. That is, when 99 | * the __PI__ identifier is seen in the running script, this procedure 100 | * gets called by the underlying Jx9 virtual machine. 101 | * This procedure is responsible of expanding the constant identifier to 102 | * the desired value (3.14 in our case). 103 | */ 104 | static void PI_Constant( 105 | unqlite_value *pValue, /* Store expanded value here */ 106 | void *pUserData /* User private data (unused in our case) */ 107 | ){ 108 | /* Expand the value of PI */ 109 | unqlite_value_double(pValue, 3.1415926535898); 110 | } 111 | /* 112 | * __TIME__: expand the current local time. 113 | * Our second constant is the __TIME__ constant. 114 | * When the __TIME__ identifier is seen in the running script, this procedure 115 | * gets called by the underlying Jx9 virtual machine. 116 | * This procedure is responsible of expanding the constant identifier to 117 | * the desired value (local time in our case). 118 | */ 119 | #include 120 | static void TIME_Constant(unqlite_value *pValue, void *pUserData /* Unused */) 121 | { 122 | struct tm *pLocal; 123 | #if defined(_MSC_VER) || defined(__MINGW32__) 124 | /* errno_t err; */ 125 | __time64_t long_time; 126 | struct tm newtime; 127 | _time64( &long_time ); 128 | /* err = */ _localtime64_s(&newtime, &long_time); 129 | /* if (err != 0) return UNQLITE_INVALID; */ 130 | pLocal = &newtime; 131 | #else 132 | time_t tt; 133 | /* Get the current local time */ 134 | time(&tt); 135 | pLocal = localtime(&tt); 136 | #endif 137 | /* Expand the current time now */ 138 | unqlite_value_string_format(pValue, "%02d:%02d:%02d", 139 | pLocal->tm_hour, 140 | pLocal->tm_min, 141 | pLocal->tm_sec 142 | ); 143 | } 144 | /* 145 | * __OS__: expand the name of the Host Operating System. 146 | * Our last constant is the __OS__ constant. 147 | * When the __OS__ identifier is seen in the running script, this procedure 148 | * gets called by the underlying Jx9 virtual machine. 149 | * This procedure is responsible of expanding the constant identifier to 150 | * the desired value (OS name in our case). 151 | */ 152 | static void OS_Constant(unqlite_value *pValue, void *pUserData /* Unused */ ) 153 | { 154 | #ifdef __WINNT__ 155 | unqlite_value_string(pValue, "Windows", -1 /*Compute input length automatically */); 156 | #else 157 | /* Assume UNIX */ 158 | unqlite_value_string(pValue, "UNIX", -1 /*Compute input length automatically */); 159 | #endif /* __WINNT__ */ 160 | } 161 | /* Forward declaration: VM output consumer callback */ 162 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */); 163 | /* 164 | * The following is the Jx9 Program to be executed later by the UnQLite VM: 165 | * 166 | * //Test the constant expansion mechanism 167 | * print '__PI__ value: ' .. __PI__ .. JX9_EOL; 168 | * print '__TIME__ value: ' .. __TIME__ .. JX9_EOL; 169 | * print '__OS__ value: ' .. __OS__ .. JX9_EOL; 170 | * When running, you should see something like that: 171 | * __PI__ value: 3.1415926535898 172 | * __TIME__ value: 15:02:27 173 | * __OS__ value: UNIX 174 | * 175 | */ 176 | #define JX9_PROG \ 177 | "print '__PI__ value: ' .. __PI__ .. JX9_EOL;"\ 178 | "print '__TIME__ value: ' .. __TIME__ .. JX9_EOL;"\ 179 | "print '__OS__ value: ' .. __OS__ .. JX9_EOL;" 180 | 181 | /* No need for command line arguments, everything is stored in-memory */ 182 | int main(void) 183 | { 184 | unqlite *pDb; /* Database handle */ 185 | unqlite_vm *pVm; /* UnQLite VM resulting from successful compilation of the target Jx9 script */ 186 | int rc; 187 | 188 | puts(zBanner); 189 | 190 | /* Open our database */ 191 | rc = unqlite_open(&pDb,":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 192 | if( rc != UNQLITE_OK ){ 193 | Fatal(0,"Out of memory"); 194 | } 195 | 196 | /* Compile our Jx9 script defined above */ 197 | rc = unqlite_compile(pDb,JX9_PROG,sizeof(JX9_PROG)-1,&pVm); 198 | if( rc != UNQLITE_OK ){ 199 | /* Compile error, extract the compiler error log */ 200 | const char *zBuf; 201 | int iLen; 202 | /* Extract error log */ 203 | unqlite_config(pDb,UNQLITE_CONFIG_JX9_ERR_LOG,&zBuf,&iLen); 204 | if( iLen > 0 ){ 205 | puts(zBuf); 206 | } 207 | Fatal(0,"Jx9 compile error"); 208 | } 209 | 210 | /* Now we have our program compiled, it's time to register our constants 211 | * and their associated C procedure. 212 | */ 213 | rc = unqlite_create_constant(pVm, "__PI__", PI_Constant, 0); 214 | if( rc != UNQLITE_OK ){ 215 | Fatal(0,"Error while installing the __PI__ constant"); 216 | } 217 | 218 | rc = unqlite_create_constant(pVm, "__TIME__", TIME_Constant, 0); 219 | if( rc != UNQLITE_OK ){ 220 | Fatal(0,"Error while installing the __TIME__ constant"); 221 | } 222 | 223 | rc = unqlite_create_constant(pVm, "__OS__", OS_Constant, 0); 224 | if( rc != UNQLITE_OK ){ 225 | Fatal(0,"Error while installing the __OS__ constant"); 226 | } 227 | 228 | /* Install a VM output consumer callback */ 229 | rc = unqlite_vm_config(pVm,UNQLITE_VM_CONFIG_OUTPUT,VmOutputConsumer,0); 230 | if( rc != UNQLITE_OK ){ 231 | Fatal(pDb,0); 232 | } 233 | 234 | /* Execute our script */ 235 | rc = unqlite_vm_exec(pVm); 236 | if( rc != UNQLITE_OK ){ 237 | Fatal(pDb,0); 238 | } 239 | 240 | /* Release our VM */ 241 | unqlite_vm_release(pVm); 242 | 243 | /* Auto-commit the transaction and close our database */ 244 | unqlite_close(pDb); 245 | return 0; 246 | } 247 | 248 | #ifdef __WINNT__ 249 | #include 250 | #else 251 | /* Assume UNIX */ 252 | #include 253 | #endif 254 | /* 255 | * The following define is used by the UNIX build process and have 256 | * no particular meaning on windows. 257 | */ 258 | #ifndef STDOUT_FILENO 259 | #define STDOUT_FILENO 1 260 | #endif 261 | /* 262 | * VM output consumer callback. 263 | * Each time the UnQLite VM generates some outputs, the following 264 | * function gets called by the underlying virtual machine to consume 265 | * the generated output. 266 | * 267 | * All this function does is redirecting the VM output to STDOUT. 268 | * This function is registered via a call to [unqlite_vm_config()] 269 | * with a configuration verb set to: UNQLITE_VM_CONFIG_OUTPUT. 270 | */ 271 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */) 272 | { 273 | #ifdef __WINNT__ 274 | BOOL rc; 275 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pOutput,(DWORD)nOutLen,0,0); 276 | if( !rc ){ 277 | /* Abort processing */ 278 | return UNQLITE_ABORT; 279 | } 280 | #else 281 | ssize_t nWr; 282 | nWr = write(STDOUT_FILENO,pOutput,nOutLen); 283 | if( nWr < 0 ){ 284 | /* Abort processing */ 285 | return UNQLITE_ABORT; 286 | } 287 | #endif /* __WINT__ */ 288 | 289 | /* All done, data was redirected to STDOUT */ 290 | return UNQLITE_OK; 291 | } 292 | -------------------------------------------------------------------------------- /example/unqlite_mp3.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_mp3_tag.c unqlite.c -o unqlite_mp3 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Introduction to Jx9 IO facility: 12 | * 13 | * The Document store to UnQLite which is used to store JSON docs (i.e. Objects, Arrays, Strings, etc.) 14 | * in the database is powered by the Jx9 programming language. 15 | * 16 | * Jx9 is an embeddable scripting language also called extension language designed 17 | * to support general procedural programming with data description facilities. 18 | * Jx9 is a Turing-Complete, dynamically typed programming language based on JSON 19 | * and implemented as a library in the UnQLite core. 20 | * 21 | * Jx9 is built with a tons of features and has a clean and familiar syntax similar 22 | * to C and Javascript. 23 | * Being an extension language, Jx9 has no notion of a main program, it only works 24 | * embedded in a host application. 25 | * The host program (UnQLite in our case) can write and read Jx9 variables and can 26 | * register C/C++ functions to be called by Jx9 code. 27 | * 28 | * For an introduction to the UnQLite C/C++ interface, please refer to: 29 | * http://unqlite.org/api_intro.html 30 | * For an introduction to Jx9, please refer to: 31 | * http://unqlite.org/jx9.html 32 | * For the full C/C++ API reference guide, please refer to: 33 | * http://unqlite.org/c_api.html 34 | * UnQLite in 5 Minutes or Less: 35 | * http://unqlite.org/intro.html 36 | * The Architecture of the UnQLite Database Engine: 37 | * http://unqlite.org/arch.html 38 | */ 39 | /* $SymiscID: unqlite_mp3_tag.c v1.0 Win7 2013-05-17 22:37 stable $ */ 40 | /* 41 | * Make sure you have the latest release of UnQLite from: 42 | * http://unqlite.org/downloads.html 43 | */ 44 | #include /* puts() */ 45 | #include /* exit() */ 46 | /* Make sure this header file is available.*/ 47 | #include "unqlite.h" 48 | /* 49 | * Banner. 50 | */ 51 | static const char zBanner[] = { 52 | "============================================================\n" 53 | "UnQLite Document-Store (Via Jx9) IO Intro (2) \n" 54 | " http://unqlite.org/\n" 55 | "============================================================\n" 56 | }; 57 | /* 58 | * Extract the database error log and exit. 59 | */ 60 | static void Fatal(unqlite *pDb,const char *zMsg) 61 | { 62 | if( pDb ){ 63 | const char *zErr; 64 | int iLen = 0; /* Stupid cc warning */ 65 | 66 | /* Extract the database error log */ 67 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 68 | if( iLen > 0 ){ 69 | /* Output the DB error log */ 70 | puts(zErr); /* Always null terminated */ 71 | } 72 | }else{ 73 | if( zMsg ){ 74 | puts(zMsg); 75 | } 76 | } 77 | /* Manually shutdown the library */ 78 | unqlite_lib_shutdown(); 79 | /* Exit immediately */ 80 | exit(0); 81 | } 82 | /* Forward declaration: VM output consumer callback */ 83 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */); 84 | /* 85 | * The following is the Jx9 Program to be executed later by the UnQLite VM: 86 | * 87 | * The program expects a command line 88 | * path from which all available MP3 are extrcated and their ID3v1 tags (if available) 89 | * are outputted. 90 | * To run this program, simply enter the executable name and the target directory as follows: 91 | * 92 | * unqlite_mp3 /path/to/my/mp3s 93 | * 94 | * if( count($argv) < 1 ){ 95 | * die("Missing MP3s directory"); 96 | * } 97 | * $dir = $argv[0]; 98 | * if( !chdir($dir) ){ 99 | * die("Error while changing directory"); 100 | * } 101 | * //Collect MP3 files 102 | * $pFiles = glob("*.mp3"); 103 | * if( is_array($pFiles) ){ 104 | * foreach($pFiles as $pEntry){ 105 | * print "\nMP3: $pEntry ",size_format(filesize($pEntry)),JX9_EOL;// Give a nice presentation 106 | * // Open the file in a read only mode 107 | * $pHandle = fopen($pEntry,'r'); 108 | * if( $pHandle == FALSE ){ 109 | * print "IO error while opening $pEntry\n"; 110 | * continue; // Ignore 111 | * } 112 | * // Seek 128 bytes from the end 113 | * fseek($pHandle,-128,SEEK_END); 114 | * // Read the 128 raw data 115 | * $zBuf = fread($pHandle,128); 116 | * if( !$zBuf || strlen($zBuf) != 128 ){ 117 | * print "$pEntry: Read error\n"; 118 | * // Ignore 119 | * continue; 120 | * } 121 | * if( $zBuf[0] == 'T' && $zBuf[1] == 'A' && $zBuf[2] == 'G' ){ 122 | * $nOfft = 3; 123 | * // Extract the title 124 | * $zTitle = substr($zBuf,$nOfft,30); 125 | * // Remove trailing and leading NUL bytes and white spaces 126 | * $zTitle = trim($zTitle); 127 | * if( strlen($zTitle) > 0 ){ 128 | * print "Title: $zTitle\n"; 129 | * } 130 | * // Extract artist name 131 | * $zArtist = substr($zBuf,$nOfft+30,30); 132 | * // Remove trailing and leading NUL bytes and white spaces 133 | * $zArtist = trim($zArtist); 134 | * if( strlen($zArtist) > 0 ){ 135 | * print "Artist: $zArtist\n"; 136 | * } 137 | * // Extract album name 138 | * $zAlbum = substr($zBuf,$nOfft+30+30,30); 139 | * // Remove trailing and leading NUL bytes and white spaces 140 | * $zAlbum = trim($zAlbum); 141 | * if( strlen($zAlbum) > 0 ){ 142 | * print "Album: $zAlbum\n"; 143 | * } 144 | * // Extract the Year 145 | * $zYear = substr($zBuf,$nOfft+30+30+30,4); 146 | * // Remove trailing and leading NUL bytes and white spaces 147 | * $zYear = trim($zYear); 148 | * if( strlen($zYear) > 0 ){ 149 | * print "Year: $zYear\n"; 150 | * } 151 | * // Next entry 152 | * print "------------------------------------------------------\n"; 153 | * } 154 | * // All done whith this file, close the handle 155 | * fclose($pHandle); 156 | * } 157 | * } 158 | * 159 | */ 160 | #define JX9_PROG \ 161 | "if( count($argv) < 1 ){"\ 162 | " EXIT('Missing MP3s directory');"\ 163 | "}"\ 164 | "$dir = $argv[0];"\ 165 | "if( !chdir($dir) ){"\ 166 | " EXIT('Error while changing directory');"\ 167 | "}"\ 168 | "/* Collect MP3 files */"\ 169 | "$pFiles = glob('*.mp3');"\ 170 | "if( is_array($pFiles) ){"\ 171 | "foreach($pFiles as $pEntry){"\ 172 | " print \"\nMP3: $pEntry \",size_format(filesize($pEntry)),JX9_EOL;/* Give a nice presentation */"\ 173 | " /* Open the file in a read only mode */"\ 174 | " $pHandle = fopen($pEntry,'r');"\ 175 | " if( $pHandle == FALSE ){"\ 176 | " print \"IO error while opening $pEntry\n\";"\ 177 | " continue; /* Ignore */"\ 178 | " }"\ 179 | " /* Seek 128 bytes from the end */"\ 180 | " fseek($pHandle,-128,SEEK_END);"\ 181 | " /* Read the 128 raw data */"\ 182 | " $zBuf = fread($pHandle,128);"\ 183 | " if( !$zBuf || strlen($zBuf) != 128 ){"\ 184 | " print \"$pEntry: Read error\n\";"\ 185 | " /* Ignore */"\ 186 | " continue;"\ 187 | " }"\ 188 | " if( $zBuf[0] == 'T' && $zBuf[1] == 'A' && $zBuf[2] == 'G' ){"\ 189 | " $nOfft = 3 /* TAG */;"\ 190 | " /* Extract the title */"\ 191 | " $zTitle = substr($zBuf,$nOfft,30);"\ 192 | " /* Remove trailing and leading NUL bytes and white spaces */"\ 193 | " $zTitle = trim($zTitle);"\ 194 | " if( strlen($zTitle) > 0 ){"\ 195 | " print \"Title: $zTitle\n\";"\ 196 | " }"\ 197 | " /* Extract artist name */"\ 198 | " $zArtist = substr($zBuf,$nOfft+30,30);"\ 199 | " /* Remove trailing and leading NUL bytes and white spaces */"\ 200 | " $zArtist = trim($zArtist);"\ 201 | " if( strlen($zArtist) > 0 ){"\ 202 | " print \"Artist: $zArtist\n\";"\ 203 | " }"\ 204 | " /* Extract album name */"\ 205 | " $zAlbum = substr($zBuf,$nOfft+30+30,30);"\ 206 | " /* Remove trailing and leading NUL bytes and white spaces */"\ 207 | " $zAlbum = trim($zAlbum);"\ 208 | " if( strlen($zAlbum) > 0 ){"\ 209 | " print \"Album: $zAlbum\n\";"\ 210 | " }"\ 211 | " /* Extract the Year */"\ 212 | " $zYear = substr($zBuf,$nOfft+30+30+30,4);"\ 213 | " /* Remove trailing and leading NUL bytes and white spaces */"\ 214 | " $zYear = trim($zYear);"\ 215 | " if( strlen($zYear) > 0 ){"\ 216 | " print \"Year: $zYear\n\";"\ 217 | " }"\ 218 | " /* Next entry */"\ 219 | " print \"------------------------------------------------------\n\";"\ 220 | " }"\ 221 | " /* All done whith this file,close the handle */"\ 222 | " fclose($pHandle);"\ 223 | " }"\ 224 | "}" 225 | 226 | 227 | int main(int argc,char *argv[]) 228 | { 229 | unqlite *pDb; /* Database handle */ 230 | unqlite_vm *pVm; /* UnQLite VM resulting from successful compilation of the target Jx9 script */ 231 | int n,rc; 232 | 233 | puts(zBanner); 234 | 235 | /* Open our database */ 236 | rc = unqlite_open(&pDb,":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 237 | if( rc != UNQLITE_OK ){ 238 | Fatal(0,"Out of memory"); 239 | } 240 | 241 | /* Compile our Jx9 script defined above */ 242 | rc = unqlite_compile(pDb,JX9_PROG,sizeof(JX9_PROG)-1,&pVm); 243 | if( rc != UNQLITE_OK ){ 244 | /* Compile error, extract the compiler error log */ 245 | const char *zBuf; 246 | int iLen; 247 | /* Extract error log */ 248 | unqlite_config(pDb,UNQLITE_CONFIG_JX9_ERR_LOG,&zBuf,&iLen); 249 | if( iLen > 0 ){ 250 | puts(zBuf); 251 | } 252 | Fatal(0,"Jx9 compile error"); 253 | } 254 | 255 | /* Register script agruments so we can access them later using the $argv[] 256 | * array from the compiled Jx9 program. 257 | */ 258 | for( n = 1; n < argc ; ++n ){ 259 | unqlite_vm_config(pVm, UNQLITE_VM_CONFIG_ARGV_ENTRY, argv[n]/* Argument value */); 260 | } 261 | 262 | /* Install a VM output consumer callback */ 263 | rc = unqlite_vm_config(pVm,UNQLITE_VM_CONFIG_OUTPUT,VmOutputConsumer,0); 264 | if( rc != UNQLITE_OK ){ 265 | Fatal(pDb,0); 266 | } 267 | 268 | /* Execute our script */ 269 | rc = unqlite_vm_exec(pVm); 270 | if( rc != UNQLITE_OK ){ 271 | Fatal(pDb,0); 272 | } 273 | 274 | /* Release our VM */ 275 | unqlite_vm_release(pVm); 276 | 277 | /* Auto-commit the transaction and close our database */ 278 | unqlite_close(pDb); 279 | return 0; 280 | } 281 | 282 | #ifdef __WINNT__ 283 | #include 284 | #else 285 | /* Assume UNIX */ 286 | #include 287 | #endif 288 | /* 289 | * The following define is used by the UNIX build process and have 290 | * no particular meaning on windows. 291 | */ 292 | #ifndef STDOUT_FILENO 293 | #define STDOUT_FILENO 1 294 | #endif 295 | /* 296 | * VM output consumer callback. 297 | * Each time the UnQLite VM generates some outputs, the following 298 | * function gets called by the underlying virtual machine to consume 299 | * the generated output. 300 | * 301 | * All this function does is redirecting the VM output to STDOUT. 302 | * This function is registered via a call to [unqlite_vm_config()] 303 | * with a configuration verb set to: UNQLITE_VM_CONFIG_OUTPUT. 304 | */ 305 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */) 306 | { 307 | #ifdef __WINNT__ 308 | BOOL rc; 309 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pOutput,(DWORD)nOutLen,0,0); 310 | if( !rc ){ 311 | /* Abort processing */ 312 | return UNQLITE_ABORT; 313 | } 314 | #else 315 | ssize_t nWr; 316 | nWr = write(STDOUT_FILENO,pOutput,nOutLen); 317 | if( nWr < 0 ){ 318 | /* Abort processing */ 319 | return UNQLITE_ABORT; 320 | } 321 | #endif /* __WINT__ */ 322 | 323 | /* All done, data was redirected to STDOUT */ 324 | return UNQLITE_OK; 325 | } 326 | -------------------------------------------------------------------------------- /src/fastjson.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. 3 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ 4 | * Version 1.1.6 5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES 6 | * please contact Symisc Systems via: 7 | * legal@symisc.net 8 | * licensing@symisc.net 9 | * contact@symisc.net 10 | * or visit: 11 | * http://unqlite.org/licensing.html 12 | */ 13 | /* $SymiscID: fastjson.c v1.1 FreeBSD 2012-12-05 22:52 stable $ */ 14 | #ifndef UNQLITE_AMALGAMATION 15 | #include "unqliteInt.h" 16 | #endif 17 | /* JSON binary encoding, decoding and stuff like that */ 18 | #ifndef UNQLITE_FAST_JSON_NEST_LIMIT 19 | #if defined(__WINNT__) || defined(__UNIXES__) 20 | #define UNQLITE_FAST_JSON_NEST_LIMIT 64 /* Nesting limit */ 21 | #else 22 | #define UNQLITE_FAST_JSON_NEST_LIMIT 32 /* Nesting limit */ 23 | #endif 24 | #endif /* UNQLITE_FAST_JSON_NEST_LIMIT */ 25 | /* 26 | * JSON to Binary using the FastJSON implementation (BigEndian). 27 | */ 28 | /* 29 | * FastJSON implemented binary token. 30 | */ 31 | #define FJSON_DOC_START 1 /* { */ 32 | #define FJSON_DOC_END 2 /* } */ 33 | #define FJSON_ARRAY_START 3 /* [ */ 34 | #define FJSON_ARRAY_END 4 /* ] */ 35 | #define FJSON_COLON 5 /* : */ 36 | #define FJSON_COMMA 6 /* , */ 37 | #define FJSON_ID 7 /* ID + 4 Bytes length */ 38 | #define FJSON_STRING 8 /* String + 4 bytes length */ 39 | #define FJSON_BYTE 9 /* Byte */ 40 | #define FJSON_INT64 10 /* Integer 64 + 8 bytes */ 41 | #define FJSON_REAL 18 /* Floating point value + 2 bytes */ 42 | #define FJSON_NULL 23 /* NULL */ 43 | #define FJSON_TRUE 24 /* TRUE */ 44 | #define FJSON_FALSE 25 /* FALSE */ 45 | /* 46 | * Encode a Jx9 value to binary JSON. 47 | */ 48 | UNQLITE_PRIVATE sxi32 FastJsonEncode( 49 | jx9_value *pValue, /* Value to encode */ 50 | SyBlob *pOut, /* Store encoded value here */ 51 | int iNest /* Nesting limit */ 52 | ) 53 | { 54 | sxi32 iType = pValue ? pValue->iFlags : MEMOBJ_NULL; 55 | sxi32 rc = SXRET_OK; 56 | int c; 57 | if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ 58 | /* Nesting limit reached */ 59 | return SXERR_LIMIT; 60 | } 61 | if( iType & (MEMOBJ_NULL|MEMOBJ_RES) ){ 62 | /* 63 | * Resources are encoded as null also. 64 | */ 65 | c = FJSON_NULL; 66 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 67 | }else if( iType & MEMOBJ_BOOL ){ 68 | c = pValue->x.iVal ? FJSON_TRUE : FJSON_FALSE; 69 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 70 | }else if( iType & MEMOBJ_STRING ){ 71 | unsigned char zBuf[sizeof(sxu32)]; /* String length */ 72 | c = FJSON_STRING; 73 | SyBigEndianPack32(zBuf,SyBlobLength(&pValue->sBlob)); 74 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 75 | if( rc == SXRET_OK ){ 76 | rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); 77 | if( rc == SXRET_OK ){ 78 | rc = SyBlobAppend(pOut,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob)); 79 | } 80 | } 81 | }else if( iType & MEMOBJ_INT ){ 82 | unsigned char zBuf[8]; 83 | /* 64bit big endian integer */ 84 | c = FJSON_INT64; 85 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 86 | if( rc == SXRET_OK ){ 87 | SyBigEndianPack64(zBuf,(sxu64)pValue->x.iVal); 88 | rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); 89 | } 90 | }else if( iType & MEMOBJ_REAL ){ 91 | /* Real number */ 92 | c = FJSON_REAL; 93 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 94 | if( rc == SXRET_OK ){ 95 | sxu32 iOfft = SyBlobLength(pOut); 96 | rc = SyBlobAppendBig16(pOut,0); 97 | if( rc == SXRET_OK ){ 98 | unsigned char *zBlob; 99 | SyBlobFormat(pOut,"%.15g",pValue->x.rVal); 100 | zBlob = (unsigned char *)SyBlobDataAt(pOut,iOfft); 101 | SyBigEndianPack16(zBlob,(sxu16)(SyBlobLength(pOut) - ( 2 + iOfft))); 102 | } 103 | } 104 | }else if( iType & MEMOBJ_HASHMAP ){ 105 | /* A JSON object or array */ 106 | jx9_hashmap *pMap = (jx9_hashmap *)pValue->x.pOther; 107 | jx9_hashmap_node *pNode; 108 | jx9_value *pEntry; 109 | /* Reset the hashmap loop cursor */ 110 | jx9HashmapResetLoopCursor(pMap); 111 | if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ 112 | jx9_value sKey; 113 | /* A JSON object */ 114 | c = FJSON_DOC_START; /* { */ 115 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 116 | if( rc == SXRET_OK ){ 117 | jx9MemObjInit(pMap->pVm,&sKey); 118 | /* Encode object entries */ 119 | while((pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ 120 | /* Extract the key */ 121 | jx9HashmapExtractNodeKey(pNode,&sKey); 122 | /* Encode it */ 123 | rc = FastJsonEncode(&sKey,pOut,iNest+1); 124 | if( rc != SXRET_OK ){ 125 | break; 126 | } 127 | c = FJSON_COLON; 128 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 129 | if( rc != SXRET_OK ){ 130 | break; 131 | } 132 | /* Extract the value */ 133 | pEntry = jx9HashmapGetNodeValue(pNode); 134 | /* Encode it */ 135 | rc = FastJsonEncode(pEntry,pOut,iNest+1); 136 | if( rc != SXRET_OK ){ 137 | break; 138 | } 139 | /* Delimit the entry */ 140 | c = FJSON_COMMA; 141 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 142 | if( rc != SXRET_OK ){ 143 | break; 144 | } 145 | } 146 | jx9MemObjRelease(&sKey); 147 | if( rc == SXRET_OK ){ 148 | c = FJSON_DOC_END; /* } */ 149 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 150 | } 151 | } 152 | }else{ 153 | /* A JSON array */ 154 | c = FJSON_ARRAY_START; /* [ */ 155 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 156 | if( rc == SXRET_OK ){ 157 | /* Encode array entries */ 158 | while( (pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ 159 | /* Extract the value */ 160 | pEntry = jx9HashmapGetNodeValue(pNode); 161 | /* Encode it */ 162 | rc = FastJsonEncode(pEntry,pOut,iNest+1); 163 | if( rc != SXRET_OK ){ 164 | break; 165 | } 166 | /* Delimit the entry */ 167 | c = FJSON_COMMA; 168 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 169 | if( rc != SXRET_OK ){ 170 | break; 171 | } 172 | } 173 | if( rc == SXRET_OK ){ 174 | c = FJSON_ARRAY_END; /* ] */ 175 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); 176 | } 177 | } 178 | } 179 | } 180 | return rc; 181 | } 182 | /* 183 | * Decode a FastJSON binary blob. 184 | */ 185 | UNQLITE_PRIVATE sxi32 FastJsonDecode( 186 | const void *pIn, /* Binary JSON */ 187 | sxu32 nByte, /* Chunk delimiter */ 188 | jx9_value *pOut, /* Decoded value */ 189 | const unsigned char **pzPtr, 190 | int iNest /* Nesting limit */ 191 | ) 192 | { 193 | const unsigned char *zIn = (const unsigned char *)pIn; 194 | const unsigned char *zEnd = &zIn[nByte]; 195 | sxi32 rc = SXRET_OK; 196 | int c; 197 | if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ 198 | /* Nesting limit reached */ 199 | return SXERR_LIMIT; 200 | } 201 | c = zIn[0]; 202 | /* Advance the stream cursor */ 203 | zIn++; 204 | /* Process the binary token */ 205 | switch(c){ 206 | case FJSON_NULL: 207 | /* null */ 208 | jx9_value_null(pOut); 209 | break; 210 | case FJSON_FALSE: 211 | /* Boolean FALSE */ 212 | jx9_value_bool(pOut,0); 213 | break; 214 | case FJSON_TRUE: 215 | /* Boolean TRUE */ 216 | jx9_value_bool(pOut,1); 217 | break; 218 | case FJSON_INT64: { 219 | /* 64Bit integer */ 220 | sxu64 iVal; 221 | /* Sanity check */ 222 | if( &zIn[8] >= zEnd ){ 223 | /* Corrupt chunk */ 224 | rc = SXERR_CORRUPT; 225 | break; 226 | } 227 | SyBigEndianUnpack64(zIn,&iVal); 228 | /* Advance the pointer */ 229 | zIn += 8; 230 | jx9_value_int64(pOut,(jx9_int64)iVal); 231 | break; 232 | } 233 | case FJSON_REAL: { 234 | /* Real number */ 235 | double iVal = 0; /* cc warning */ 236 | sxu16 iLen; 237 | /* Sanity check */ 238 | if( &zIn[2] >= zEnd ){ 239 | /* Corrupt chunk */ 240 | rc = SXERR_CORRUPT; 241 | break; 242 | } 243 | SyBigEndianUnpack16(zIn,&iLen); 244 | if( &zIn[iLen] >= zEnd ){ 245 | /* Corrupt chunk */ 246 | rc = SXERR_CORRUPT; 247 | break; 248 | } 249 | zIn += 2; 250 | SyStrToReal((const char *)zIn,(sxu32)iLen,&iVal,0); 251 | /* Advance the pointer */ 252 | zIn += iLen; 253 | jx9_value_double(pOut,iVal); 254 | break; 255 | } 256 | case FJSON_STRING: { 257 | /* UTF-8/Binary chunk */ 258 | sxu32 iLength; 259 | /* Sanity check */ 260 | if( &zIn[4] >= zEnd ){ 261 | /* Corrupt chunk */ 262 | rc = SXERR_CORRUPT; 263 | break; 264 | } 265 | SyBigEndianUnpack32(zIn,&iLength); 266 | if( &zIn[iLength] >= zEnd ){ 267 | /* Corrupt chunk */ 268 | rc = SXERR_CORRUPT; 269 | break; 270 | } 271 | zIn += 4; 272 | /* Invalidate any prior representation */ 273 | if( pOut->iFlags & MEMOBJ_STRING ){ 274 | /* Reset the string cursor */ 275 | SyBlobReset(&pOut->sBlob); 276 | } 277 | rc = jx9MemObjStringAppend(pOut,(const char *)zIn,iLength); 278 | /* Update pointer */ 279 | zIn += iLength; 280 | break; 281 | } 282 | case FJSON_ARRAY_START: { 283 | /* Binary JSON array */ 284 | jx9_hashmap *pMap; 285 | jx9_value sVal; 286 | /* Allocate a new hashmap */ 287 | pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); 288 | if( pMap == 0 ){ 289 | rc = SXERR_MEM; 290 | break; 291 | } 292 | jx9MemObjInit(pOut->pVm,&sVal); 293 | jx9MemObjRelease(pOut); 294 | MemObjSetType(pOut,MEMOBJ_HASHMAP); 295 | pOut->x.pOther = pMap; 296 | rc = SXRET_OK; 297 | for(;;){ 298 | /* Jump leading binary commas */ 299 | while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ 300 | zIn++; 301 | } 302 | if( zIn >= zEnd || zIn[0] == FJSON_ARRAY_END ){ 303 | if( zIn < zEnd ){ 304 | zIn++; /* Jump the trailing binary ] */ 305 | } 306 | break; 307 | } 308 | /* Decode the value */ 309 | rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); 310 | if( rc != SXRET_OK ){ 311 | break; 312 | } 313 | /* Insert the decoded value */ 314 | rc = jx9HashmapInsert(pMap,0,&sVal); 315 | if( rc != UNQLITE_OK ){ 316 | break; 317 | } 318 | } 319 | if( rc != SXRET_OK ){ 320 | jx9MemObjRelease(pOut); 321 | } 322 | jx9MemObjRelease(&sVal); 323 | break; 324 | } 325 | case FJSON_DOC_START: { 326 | /* Binary JSON object */ 327 | jx9_value sVal,sKey; 328 | jx9_hashmap *pMap; 329 | /* Allocate a new hashmap */ 330 | pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); 331 | if( pMap == 0 ){ 332 | rc = SXERR_MEM; 333 | break; 334 | } 335 | jx9MemObjInit(pOut->pVm,&sVal); 336 | jx9MemObjInit(pOut->pVm,&sKey); 337 | jx9MemObjRelease(pOut); 338 | MemObjSetType(pOut,MEMOBJ_HASHMAP); 339 | pOut->x.pOther = pMap; 340 | rc = SXRET_OK; 341 | for(;;){ 342 | /* Jump leading binary commas */ 343 | while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ 344 | zIn++; 345 | } 346 | if( zIn >= zEnd || zIn[0] == FJSON_DOC_END ){ 347 | if( zIn < zEnd ){ 348 | zIn++; /* Jump the trailing binary } */ 349 | } 350 | break; 351 | } 352 | /* Extract the key */ 353 | rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sKey,&zIn,iNest+1); 354 | if( rc != UNQLITE_OK ){ 355 | break; 356 | } 357 | if( zIn >= zEnd || zIn[0] != FJSON_COLON ){ 358 | rc = UNQLITE_CORRUPT; 359 | break; 360 | } 361 | zIn++; /* Jump the binary colon ':' */ 362 | if( zIn >= zEnd ){ 363 | rc = UNQLITE_CORRUPT; 364 | break; 365 | } 366 | /* Decode the value */ 367 | rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); 368 | if( rc != SXRET_OK ){ 369 | break; 370 | } 371 | /* Insert the key and its associated value */ 372 | rc = jx9HashmapInsert(pMap,&sKey,&sVal); 373 | if( rc != UNQLITE_OK ){ 374 | break; 375 | } 376 | } 377 | if( rc != SXRET_OK ){ 378 | jx9MemObjRelease(pOut); 379 | } 380 | jx9MemObjRelease(&sVal); 381 | jx9MemObjRelease(&sKey); 382 | break; 383 | } 384 | default: 385 | /* Corrupt data */ 386 | rc = SXERR_CORRUPT; 387 | break; 388 | } 389 | if( pzPtr ){ 390 | *pzPtr = zIn; 391 | } 392 | return rc; 393 | } 394 | -------------------------------------------------------------------------------- /example/6.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_hostapp_info.c unqlite.c -o unqlite_host 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Sharing data between the host application and the underlying Jx9 script 12 | * via the document-store interface to UnQLite: 13 | * 14 | * The Document store to UnQLite which is used to store JSON docs (i.e. Objects, Arrays, Strings, etc.) 15 | * in the database is powered by the Jx9 programming language. 16 | * 17 | * Jx9 is an embeddable scripting language also called extension language designed 18 | * to support general procedural programming with data description facilities. 19 | * Jx9 is a Turing-Complete, dynamically typed programming language based on JSON 20 | * and implemented as a library in the UnQLite core. 21 | * 22 | * Jx9 is built with a tons of features and has a clean and familiar syntax similar 23 | * to C and Javascript. 24 | * Being an extension language, Jx9 has no notion of a main program, it only works 25 | * embedded in a host application. 26 | * The host program (UnQLite in our case) can write and read Jx9 variables and can 27 | * register C/C++ functions to be called by Jx9 code. 28 | * 29 | * For an introduction to the UnQLite C/C++ interface, please refer to: 30 | * http://unqlite.org/api_intro.html 31 | * For an introduction to Jx9, please refer to: 32 | * http://unqlite.org/unqlite.html 33 | * For the full C/C++ API reference guide, please refer to: 34 | * http://unqlite.org/c_api.html 35 | * UnQLite in 5 Minutes or Less: 36 | * http://unqlite.org/intro.html 37 | * The Architecture of the UnQLite Database Engine: 38 | * http://unqlite.org/arch.html 39 | */ 40 | /* $SymiscID: unqlite_hostapp_info.c v1.0 Win7 2013-05-17 22:37 stable $ */ 41 | /* 42 | * Make sure you have the latest release of UnQLite from: 43 | * http://unqlite.org/downloads.html 44 | */ 45 | #include /* puts() */ 46 | #include /* exit() */ 47 | /* Make sure this header file is available.*/ 48 | #include "unqlite.h" 49 | /* 50 | * Banner. 51 | */ 52 | static const char zBanner[] = { 53 | "============================================================\n" 54 | "UnQLite Document-Store (Via Jx9) Data Share Intro \n" 55 | " http://unqlite.org/\n" 56 | "============================================================\n" 57 | }; 58 | /* 59 | * Extract the database error log and exit. 60 | */ 61 | static void Fatal(unqlite *pDb,const char *zMsg) 62 | { 63 | if( pDb ){ 64 | const char *zErr; 65 | int iLen = 0; /* Stupid cc warning */ 66 | 67 | /* Extract the database error log */ 68 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 69 | if( iLen > 0 ){ 70 | /* Output the DB error log */ 71 | puts(zErr); /* Always null terminated */ 72 | } 73 | }else{ 74 | if( zMsg ){ 75 | puts(zMsg); 76 | } 77 | } 78 | /* Manually shutdown the library */ 79 | unqlite_lib_shutdown(); 80 | /* Exit immediately */ 81 | exit(0); 82 | } 83 | /* 84 | * The following walker callback is made available to the [unqlite_array_walk()] interface 85 | * which is used to iterate over the JSON object extracted from the running script. 86 | * (See below for more information). 87 | */ 88 | static int JsonObjectWalker(unqlite_value *pKey,unqlite_value *pData,void *pUserData /* Unused */) 89 | { 90 | const char *zKey,*zData; 91 | /* Extract the key and the data field */ 92 | zKey = unqlite_value_to_string(pKey,0); 93 | zData = unqlite_value_to_string(pData,0); 94 | /* Dump */ 95 | printf( 96 | "%s ===> %s\n", 97 | zKey, 98 | zData 99 | ); 100 | return UNQLITE_OK; 101 | } 102 | /* Forward declaration: VM output consumer callback */ 103 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */); 104 | /* 105 | * The following is the Jx9 Program to be executed later by the UnQLite VM: 106 | * 107 | * This program demonstrate how data is shared between the host application 108 | * and the running JX9 script. The main() function defined below creates and install 109 | * two foreign variables named respectively $my_app and $my_data. The first is a simple 110 | * scalar value while the last is a complex JSON object. these foreign variables are 111 | * made available to the running script using the [unqlite_vm_config()] interface with 112 | * a configuration verb set to UNQLITE_VM_CONFIG_CREATE_VAR. 113 | * 114 | * Jx9 Program: 115 | * 116 | * print "Showing foreign variables contents\n"; 117 | * //Scalar foreign variable named $my_app 118 | * print "\$my_app =",$my_app..JX9_EOL; 119 | * //Foreign JSON object named $my_data 120 | * print "\$my_data = ",$my_data; 121 | * //Dump command line arguments 122 | * if( count($argv) > 0 ){ 123 | * print "\nCommand line arguments:\n"; 124 | * print $argv; 125 | * }else{ 126 | * print "\nEmpty command line"; 127 | * } 128 | * //Return a simple JSON object to the host application 129 | * $my_config = { 130 | * "unqlite_signature" : db_sig(), //UnQLite Unique signature 131 | * "time" : __TIME__, //Current time 132 | * "date" : __DATE__ //Current date 133 | * }; 134 | */ 135 | #define JX9_PROG \ 136 | "print \"Showing foreign variables contents\n\n\";"\ 137 | " /*Scalar foreign variable named $my_app*/"\ 138 | " print \"\\$my_app = \",$my_app..JX9_EOL;"\ 139 | " /*JSON object foreign variable named $my_data*/"\ 140 | " print \"\n\\$my_data = \",$my_data..JX9_EOL;"\ 141 | " /*Dump command line arguments*/"\ 142 | " if( count($argv) > 0 ){"\ 143 | " print \"\nCommand line arguments:\n\";"\ 144 | " print $argv..JX9_EOL;"\ 145 | " }else{"\ 146 | " print \"\nEmpty command line\";"\ 147 | " }"\ 148 | " /*Return a simple JSON object to the host application*/"\ 149 | " $my_config = {"\ 150 | " 'unqlite_signature' : db_sig(), /* UnQLite Unique version*/"\ 151 | " 'time' : __TIME__, /*Current time*/"\ 152 | " 'date' : __DATE__ /*Current date*/"\ 153 | " };" 154 | 155 | int main(int argc,char *argv[]) 156 | { 157 | unqlite_value *pScalar,*pObject; /* Foreign Jx9 variable to be installed later */ 158 | unqlite *pDb; /* Database handle */ 159 | unqlite_vm *pVm; /* UnQLite VM resulting from successful compilation of the target Jx9 script */ 160 | int n,rc; 161 | 162 | puts(zBanner); 163 | 164 | /* Open our database */ 165 | rc = unqlite_open(&pDb,":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 166 | if( rc != UNQLITE_OK ){ 167 | Fatal(0,"Out of memory"); 168 | } 169 | 170 | /* Compile our Jx9 script defined above */ 171 | rc = unqlite_compile(pDb,JX9_PROG,sizeof(JX9_PROG)-1,&pVm); 172 | if( rc != UNQLITE_OK ){ 173 | /* Compile error, extract the compiler error log */ 174 | const char *zBuf; 175 | int iLen; 176 | /* Extract error log */ 177 | unqlite_config(pDb,UNQLITE_CONFIG_JX9_ERR_LOG,&zBuf,&iLen); 178 | if( iLen > 0 ){ 179 | puts(zBuf); 180 | } 181 | Fatal(0,"Jx9 compile error"); 182 | } 183 | 184 | /* Register script agruments so we can access them later using the $argv[] 185 | * array from the compiled Jx9 program. 186 | */ 187 | for( n = 1; n < argc ; ++n ){ 188 | unqlite_vm_config(pVm, UNQLITE_VM_CONFIG_ARGV_ENTRY, argv[n]/* Argument value */); 189 | } 190 | 191 | /* Install a VM output consumer callback */ 192 | rc = unqlite_vm_config(pVm,UNQLITE_VM_CONFIG_OUTPUT,VmOutputConsumer,0); 193 | if( rc != UNQLITE_OK ){ 194 | Fatal(pDb,0); 195 | } 196 | 197 | /* 198 | * Create a simple scalar variable. 199 | */ 200 | pScalar = unqlite_vm_new_scalar(pVm); 201 | if( pScalar == 0 ){ 202 | Fatal(0,"Cannot create foreign variable $my_app"); 203 | } 204 | /* Populate the variable with the desired information */ 205 | unqlite_value_string(pScalar,"My Host Application/1.2.5",-1/*Compule length automatically*/); /* Dummy signature*/ 206 | /* 207 | * Install the variable ($my_app). 208 | */ 209 | rc = unqlite_vm_config( 210 | pVm, 211 | UNQLITE_VM_CONFIG_CREATE_VAR, /* Create variable command */ 212 | "my_app", /* Variable name (without the dollar sign) */ 213 | pScalar /* Value */ 214 | ); 215 | if( rc != UNQLITE_OK ){ 216 | Fatal(0,"Error while installing $my_app"); 217 | } 218 | /* To access this foreign variable from the running script, simply invoke it 219 | * as follows: 220 | * print $my_app; 221 | * or 222 | * dump($my_app); 223 | */ 224 | 225 | /* 226 | * Now, it's time to create and install a more complex variable which is a JSON 227 | * object named $my_data. 228 | * The JSON Object looks like this: 229 | * { 230 | * "path" : "/usr/local/etc", 231 | * "port" : 8082, 232 | * "fork" : true 233 | * }; 234 | */ 235 | pObject = unqlite_vm_new_array(pVm); /* Unified interface for JSON Objects and Arrays */ 236 | /* Populate the object with the fields defined above. 237 | */ 238 | unqlite_value_reset_string_cursor(pScalar); 239 | 240 | /* Add the "path" : "/usr/local/etc" entry */ 241 | unqlite_value_string(pScalar,"/usr/local/etc",-1); 242 | unqlite_array_add_strkey_elem(pObject,"path",pScalar); /* Will make it's own copy of pScalar */ 243 | 244 | /* Add the "port" : 8080 entry */ 245 | unqlite_value_int(pScalar,8080); 246 | unqlite_array_add_strkey_elem(pObject,"port",pScalar); /* Will make it's own copy of pScalar */ 247 | 248 | /* Add the "fork": true entry */ 249 | unqlite_value_bool(pScalar,1 /* TRUE */); 250 | unqlite_array_add_strkey_elem(pObject,"fork",pScalar); /* Will make it's own copy of pScalar */ 251 | 252 | /* Now, install the variable and associate the JSON object with it */ 253 | rc = unqlite_vm_config( 254 | pVm, 255 | UNQLITE_VM_CONFIG_CREATE_VAR, /* Create variable command */ 256 | "my_data", /* Variable name (without the dollar sign) */ 257 | pObject /*value */ 258 | ); 259 | if( rc != UNQLITE_OK ){ 260 | Fatal(0,"Error while installing $my_data"); 261 | } 262 | 263 | /* Release the two values */ 264 | unqlite_vm_release_value(pVm,pScalar); 265 | unqlite_vm_release_value(pVm,pObject); 266 | 267 | /* Execute our script */ 268 | unqlite_vm_exec(pVm); 269 | 270 | /* Extract the content of the variable named $my_config defined in the 271 | * running script which hold a simple JSON object. 272 | */ 273 | pObject = unqlite_vm_extract_variable(pVm,"my_config"); 274 | if( pObject && unqlite_value_is_json_object(pObject) ){ 275 | /* Iterate over object fields */ 276 | printf("\n\nTotal fields in $my_config = %u\n",unqlite_array_count(pObject)); 277 | unqlite_array_walk(pObject,JsonObjectWalker,0); 278 | } 279 | 280 | /* Release our VM */ 281 | unqlite_vm_release(pVm); 282 | 283 | /* Auto-commit the transaction and close our database */ 284 | unqlite_close(pDb); 285 | return 0; 286 | } 287 | 288 | #ifdef __WINNT__ 289 | #include 290 | #else 291 | /* Assume UNIX */ 292 | #include 293 | #endif 294 | /* 295 | * The following define is used by the UNIX build process and have 296 | * no particular meaning on windows. 297 | */ 298 | #ifndef STDOUT_FILENO 299 | #define STDOUT_FILENO 1 300 | #endif 301 | /* 302 | * VM output consumer callback. 303 | * Each time the UnQLite VM generates some outputs, the following 304 | * function gets called by the underlying virtual machine to consume 305 | * the generated output. 306 | * 307 | * All this function does is redirecting the VM output to STDOUT. 308 | * This function is registered via a call to [unqlite_vm_config()] 309 | * with a configuration verb set to: UNQLITE_VM_CONFIG_OUTPUT. 310 | */ 311 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */) 312 | { 313 | #ifdef __WINNT__ 314 | BOOL rc; 315 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pOutput,(DWORD)nOutLen,0,0); 316 | if( !rc ){ 317 | /* Abort processing */ 318 | return UNQLITE_ABORT; 319 | } 320 | #else 321 | ssize_t nWr; 322 | nWr = write(STDOUT_FILENO,pOutput,nOutLen); 323 | if( nWr < 0 ){ 324 | /* Abort processing */ 325 | return UNQLITE_ABORT; 326 | } 327 | #endif /* __WINT__ */ 328 | 329 | /* All done, data was redirected to STDOUT */ 330 | return UNQLITE_OK; 331 | } 332 | -------------------------------------------------------------------------------- /src/unqliteInt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. 3 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ 4 | * Version 1.1.6 5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES 6 | * please contact Symisc Systems via: 7 | * legal@symisc.net 8 | * licensing@symisc.net 9 | * contact@symisc.net 10 | * or visit: 11 | * http://unqlite.org/licensing.html 12 | */ 13 | /* $SymiscID: unqliteInt.h v1.7 FreeBSD 2012-11-02 11:25 devel $ */ 14 | #ifndef __UNQLITEINT_H__ 15 | #define __UNQLITEINT_H__ 16 | /* Internal interface definitions for UnQLite. */ 17 | #ifdef UNQLITE_AMALGAMATION 18 | /* Marker for routines not intended for external use */ 19 | #define UNQLITE_PRIVATE static 20 | #define JX9_AMALGAMATION 21 | #else 22 | #define UNQLITE_PRIVATE 23 | #include "unqlite.h" 24 | #include "jx9Int.h" 25 | #endif 26 | /* forward declaration */ 27 | typedef struct unqlite_db unqlite_db; 28 | /* 29 | ** The following values may be passed as the second argument to 30 | ** UnqliteOsLock(). The various locks exhibit the following semantics: 31 | ** 32 | ** SHARED: Any number of processes may hold a SHARED lock simultaneously. 33 | ** RESERVED: A single process may hold a RESERVED lock on a file at 34 | ** any time. Other processes may hold and obtain new SHARED locks. 35 | ** PENDING: A single process may hold a PENDING lock on a file at 36 | ** any one time. Existing SHARED locks may persist, but no new 37 | ** SHARED locks may be obtained by other processes. 38 | ** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. 39 | ** 40 | ** PENDING_LOCK may not be passed directly to UnqliteOsLock(). Instead, a 41 | ** process that requests an EXCLUSIVE lock may actually obtain a PENDING 42 | ** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to 43 | ** UnqliteOsLock(). 44 | */ 45 | #define NO_LOCK 0 46 | #define SHARED_LOCK 1 47 | #define RESERVED_LOCK 2 48 | #define PENDING_LOCK 3 49 | #define EXCLUSIVE_LOCK 4 50 | /* 51 | * UnQLite Locking Strategy (Same as SQLite3) 52 | * 53 | * The following #defines specify the range of bytes used for locking. 54 | * SHARED_SIZE is the number of bytes available in the pool from which 55 | * a random byte is selected for a shared lock. The pool of bytes for 56 | * shared locks begins at SHARED_FIRST. 57 | * 58 | * The same locking strategy and byte ranges are used for Unix and Windows. 59 | * This leaves open the possiblity of having clients on winNT, and 60 | * unix all talking to the same shared file and all locking correctly. 61 | * To do so would require that samba (or whatever 62 | * tool is being used for file sharing) implements locks correctly between 63 | * windows and unix. I'm guessing that isn't likely to happen, but by 64 | * using the same locking range we are at least open to the possibility. 65 | * 66 | * Locking in windows is mandatory. For this reason, we cannot store 67 | * actual data in the bytes used for locking. The pager never allocates 68 | * the pages involved in locking therefore. SHARED_SIZE is selected so 69 | * that all locks will fit on a single page even at the minimum page size. 70 | * PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE 71 | * is set high so that we don't have to allocate an unused page except 72 | * for very large databases. But one should test the page skipping logic 73 | * by setting PENDING_BYTE low and running the entire regression suite. 74 | * 75 | * Changing the value of PENDING_BYTE results in a subtly incompatible 76 | * file format. Depending on how it is changed, you might not notice 77 | * the incompatibility right away, even running a full regression test. 78 | * The default location of PENDING_BYTE is the first byte past the 79 | * 1GB boundary. 80 | */ 81 | #define PENDING_BYTE (0x40000000) 82 | #define RESERVED_BYTE (PENDING_BYTE+1) 83 | #define SHARED_FIRST (PENDING_BYTE+2) 84 | #define SHARED_SIZE 510 85 | /* 86 | * The default size of a disk sector in bytes. 87 | */ 88 | #ifndef UNQLITE_DEFAULT_SECTOR_SIZE 89 | #define UNQLITE_DEFAULT_SECTOR_SIZE 512 90 | #endif 91 | /* 92 | * Each open database file is managed by a separate instance 93 | * of the "Pager" structure. 94 | */ 95 | typedef struct Pager Pager; 96 | /* 97 | * Each database file to be accessed by the system is an instance 98 | * of the following structure. 99 | */ 100 | struct unqlite_db 101 | { 102 | Pager *pPager; /* Pager and Transaction manager */ 103 | jx9 *pJx9; /* Jx9 Engine handle */ 104 | unqlite_kv_cursor *pCursor; /* Database cursor for common usage */ 105 | }; 106 | /* 107 | * Each database connection is an instance of the following structure. 108 | */ 109 | struct unqlite 110 | { 111 | SyMemBackend sMem; /* Memory allocator subsystem */ 112 | SyBlob sErr; /* Error log */ 113 | unqlite_db sDB; /* Storage backend */ 114 | #if defined(UNQLITE_ENABLE_THREADS) 115 | const SyMutexMethods *pMethods; /* Mutex methods */ 116 | SyMutex *pMutex; /* Per-handle mutex */ 117 | #endif 118 | unqlite_vm *pVms; /* List of active VM */ 119 | sxi32 iVm; /* Total number of active VM */ 120 | sxi32 iFlags; /* Control flags (See below) */ 121 | unqlite *pNext,*pPrev; /* List of active DB handles */ 122 | sxu32 nMagic; /* Sanity check against misuse */ 123 | }; 124 | #define UNQLITE_FL_DISABLE_AUTO_COMMIT 0x001 /* Disable auto-commit on close */ 125 | /* 126 | * VM control flags (Mostly related to collection handling). 127 | */ 128 | #define UNQLITE_VM_COLLECTION_CREATE 0x001 /* Create a new collection */ 129 | #define UNQLITE_VM_COLLECTION_EXISTS 0x002 /* Exists old collection */ 130 | #define UNQLITE_VM_AUTO_LOAD 0x004 /* Auto load a collection from the vfs */ 131 | /* Forward declaration */ 132 | typedef struct unqlite_col_record unqlite_col_record; 133 | typedef struct unqlite_col unqlite_col; 134 | /* 135 | * Each an in-memory collection record is stored in an instance 136 | * of the following structure. 137 | */ 138 | struct unqlite_col_record 139 | { 140 | unqlite_col *pCol; /* Collecion this record belong */ 141 | jx9_int64 nId; /* Unique record ID */ 142 | jx9_value sValue; /* In-memory value of the record */ 143 | unqlite_col_record *pNextCol,*pPrevCol; /* Collision chain */ 144 | unqlite_col_record *pNext,*pPrev; /* Linked list of records */ 145 | }; 146 | /* 147 | * Magic number to identify a valid collection on disk. 148 | */ 149 | #define UNQLITE_COLLECTION_MAGIC 0x611E /* sizeof(unsigned short) 2 bytes */ 150 | /* 151 | * A loaded collection is identified by an instance of the following structure. 152 | */ 153 | struct unqlite_col 154 | { 155 | unqlite_vm *pVm; /* VM that own this instance */ 156 | SyString sName; /* ID of the collection */ 157 | sxu32 nHash; /* sName hash */ 158 | jx9_value sSchema; /* Collection schema */ 159 | sxu32 nSchemaOfft; /* Shema offset in sHeader */ 160 | SyBlob sWorker; /* General purpose working buffer */ 161 | SyBlob sHeader; /* Collection binary header */ 162 | jx9_int64 nLastid; /* Last collection record ID */ 163 | jx9_int64 nCurid; /* Current record ID */ 164 | jx9_int64 nTotRec; /* Total number of records in the collection */ 165 | int iFlags; /* Control flags (see below) */ 166 | unqlite_col_record **apRecord; /* Hashtable of loaded records */ 167 | unqlite_col_record *pList; /* Linked list of records */ 168 | sxu32 nRec; /* Total number of records in apRecord[] */ 169 | sxu32 nRecSize; /* apRecord[] size */ 170 | Sytm sCreation; /* Colleation creation time */ 171 | unqlite_kv_cursor *pCursor; /* Cursor pointing to the raw binary data */ 172 | unqlite_col *pNext,*pPrev; /* Next and previous collection in the chain */ 173 | unqlite_col *pNextCol,*pPrevCol; /* Collision chain */ 174 | }; 175 | /* 176 | * Each unQLite Virtual Machine resulting from successful compilation of 177 | * a Jx9 script is represented by an instance of the following structure. 178 | */ 179 | struct unqlite_vm 180 | { 181 | unqlite *pDb; /* Database handle that own this instance */ 182 | SyMemBackend sAlloc; /* Private memory allocator */ 183 | #if defined(UNQLITE_ENABLE_THREADS) 184 | SyMutex *pMutex; /* Recursive mutex associated with this VM. */ 185 | #endif 186 | unqlite_col **apCol; /* Table of loaded collections */ 187 | unqlite_col *pCol; /* List of loaded collections */ 188 | sxu32 iCol; /* Total number of loaded collections */ 189 | sxu32 iColSize; /* apCol[] size */ 190 | jx9_vm *pJx9Vm; /* Compiled Jx9 script*/ 191 | unqlite_vm *pNext,*pPrev; /* Linked list of active unQLite VM */ 192 | sxu32 nMagic; /* Magic number to avoid misuse */ 193 | }; 194 | /* 195 | * Database signature to identify a valid database image. 196 | */ 197 | #define UNQLITE_DB_SIG "unqlite" 198 | /* 199 | * Database magic number (4 bytes). 200 | */ 201 | #define UNQLITE_DB_MAGIC 0xDB7C2712 202 | /* 203 | * Maximum page size in bytes. 204 | */ 205 | #ifdef UNQLITE_MAX_PAGE_SIZE 206 | # undef UNQLITE_MAX_PAGE_SIZE 207 | #endif 208 | #define UNQLITE_MAX_PAGE_SIZE 65536 /* 65K */ 209 | /* 210 | * Minimum page size in bytes. 211 | */ 212 | #ifdef UNQLITE_MIN_PAGE_SIZE 213 | # undef UNQLITE_MIN_PAGE_SIZE 214 | #endif 215 | #define UNQLITE_MIN_PAGE_SIZE 512 216 | /* 217 | * The default size of a database page. 218 | */ 219 | #ifndef UNQLITE_DEFAULT_PAGE_SIZE 220 | # undef UNQLITE_DEFAULT_PAGE_SIZE 221 | #endif 222 | # define UNQLITE_DEFAULT_PAGE_SIZE 4096 /* 4K */ 223 | /* Forward declaration */ 224 | typedef struct Bitvec Bitvec; 225 | /* Private library functions */ 226 | /* api.c */ 227 | UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void); 228 | UNQLITE_PRIVATE int unqliteDataConsumer( 229 | const void *pOut, /* Data to consume */ 230 | unsigned int nLen, /* Data length */ 231 | void *pUserData /* User private data */ 232 | ); 233 | UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( 234 | const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ 235 | sxu32 nByte /* zName length */ 236 | ); 237 | UNQLITE_PRIVATE int unqliteGetPageSize(void); 238 | UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr); 239 | UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...); 240 | UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb); 241 | /* unql_vm.c */ 242 | UNQLITE_PRIVATE int unqliteExistsCollection(unqlite_vm *pVm, SyString *pName); 243 | UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName); 244 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol); 245 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol); 246 | UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId); 247 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol); 248 | UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol); 249 | UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue); 250 | UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue); 251 | UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag); 252 | UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue); 253 | UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag); 254 | UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err); 255 | UNQLITE_PRIVATE int unqliteCollectionUpdateRecord(unqlite_col *pCol,jx9_int64 nId, jx9_value *pValue,int iFlag); 256 | UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol); 257 | /* unql_jx9.c */ 258 | UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm); 259 | /* fastjson.c */ 260 | UNQLITE_PRIVATE sxi32 FastJsonEncode( 261 | jx9_value *pValue, /* Value to encode */ 262 | SyBlob *pOut, /* Store encoded value here */ 263 | int iNest /* Nesting limit */ 264 | ); 265 | UNQLITE_PRIVATE sxi32 FastJsonDecode( 266 | const void *pIn, /* Binary JSON */ 267 | sxu32 nByte, /* Chunk delimiter */ 268 | jx9_value *pOut, /* Decoded value */ 269 | const unsigned char **pzPtr, 270 | int iNest /* Nesting limit */ 271 | ); 272 | /* vfs.c [io_win.c, io_unix.c ] */ 273 | UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void); 274 | /* mem_kv.c */ 275 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void); 276 | /* lhash_kv.c */ 277 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void); 278 | /* os.c */ 279 | UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset); 280 | UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset); 281 | UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size); 282 | UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags); 283 | UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize); 284 | UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType); 285 | UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType); 286 | UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut); 287 | UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id); 288 | UNQLITE_PRIVATE int unqliteOsOpen( 289 | unqlite_vfs *pVfs, 290 | SyMemBackend *pAlloc, 291 | const char *zPath, 292 | unqlite_file **ppOut, 293 | unsigned int flags 294 | ); 295 | UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId); 296 | UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync); 297 | UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut); 298 | /* bitmap.c */ 299 | UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize); 300 | UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i); 301 | UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i); 302 | UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p); 303 | /* pager.c */ 304 | UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut); 305 | UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur); 306 | UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage); 307 | UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager); 308 | UNQLITE_PRIVATE int unqlitePagerOpen( 309 | unqlite_vfs *pVfs, /* The virtual file system to use */ 310 | unqlite *pDb, /* Database handle */ 311 | const char *zFilename, /* Name of the database file to open */ 312 | unsigned int iFlags /* flags controlling this file */ 313 | ); 314 | UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods); 315 | UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb); 316 | UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager); 317 | UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager); 318 | UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine); 319 | UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen); 320 | UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager); 321 | #endif /* __UNQLITEINT_H__ */ 322 | -------------------------------------------------------------------------------- /src/mem_kv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. 3 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ 4 | * Version 1.1.6 5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES 6 | * please contact Symisc Systems via: 7 | * legal@symisc.net 8 | * licensing@symisc.net 9 | * contact@symisc.net 10 | * or visit: 11 | * http://unqlite.org/licensing.html 12 | */ 13 | /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable $ */ 14 | #ifndef UNQLITE_AMALGAMATION 15 | #include "unqliteInt.h" 16 | #endif 17 | /* 18 | * This file implements an in-memory key value storage engine for unQLite. 19 | * Note that this storage engine does not support transactions. 20 | * 21 | * Normaly, I (chm@symisc.net) planned to implement a red-black tree 22 | * which is suitable for this kind of operation, but due to the lack 23 | * of time, I decided to implement a tunned hashtable which everybody 24 | * know works very well for this kind of operation. 25 | * Again, I insist on a red-black tree implementation for future version 26 | * of Unqlite. 27 | */ 28 | /* Forward declaration */ 29 | typedef struct mem_hash_kv_engine mem_hash_kv_engine; 30 | /* 31 | * Each record is storead in an instance of the following structure. 32 | */ 33 | typedef struct mem_hash_record mem_hash_record; 34 | struct mem_hash_record 35 | { 36 | mem_hash_kv_engine *pEngine; /* Storage engine */ 37 | sxu32 nHash; /* Hash of the key */ 38 | const void *pKey; /* Key */ 39 | sxu32 nKeyLen; /* Key size (Max 1GB) */ 40 | const void *pData; /* Data */ 41 | sxu32 nDataLen; /* Data length (Max 4GB) */ 42 | mem_hash_record *pNext,*pPrev; /* Link to other records */ 43 | mem_hash_record *pNextHash,*pPrevHash; /* Collision link */ 44 | }; 45 | /* 46 | * Each in-memory KV engine is represented by an instance 47 | * of the following structure. 48 | */ 49 | struct mem_hash_kv_engine 50 | { 51 | const unqlite_kv_io *pIo; /* IO methods: MUST be first */ 52 | /* Private data */ 53 | SyMemBackend sAlloc; /* Private memory allocator */ 54 | ProcHash xHash; /* Default hash function */ 55 | ProcCmp xCmp; /* Default comparison function */ 56 | sxu32 nRecord; /* Total number of records */ 57 | sxu32 nBucket; /* Bucket size: Must be a power of two */ 58 | mem_hash_record **apBucket; /* Hash bucket */ 59 | mem_hash_record *pFirst; /* First inserted entry */ 60 | mem_hash_record *pLast; /* Last inserted entry */ 61 | }; 62 | /* 63 | * Allocate a new hash record. 64 | */ 65 | static mem_hash_record * MemHashNewRecord( 66 | mem_hash_kv_engine *pEngine, 67 | const void *pKey,int nKey, 68 | const void *pData,unqlite_int64 nData, 69 | sxu32 nHash 70 | ) 71 | { 72 | SyMemBackend *pAlloc = &pEngine->sAlloc; 73 | mem_hash_record *pRecord; 74 | void *pDupData; 75 | sxu32 nByte; 76 | char *zPtr; 77 | 78 | /* Total number of bytes to alloc */ 79 | nByte = sizeof(mem_hash_record) + nKey; 80 | /* Allocate a new instance */ 81 | pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte); 82 | if( pRecord == 0 ){ 83 | return 0; 84 | } 85 | pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData); 86 | if( pDupData == 0 ){ 87 | SyMemBackendFree(pAlloc,pRecord); 88 | return 0; 89 | } 90 | zPtr = (char *)pRecord; 91 | zPtr += sizeof(mem_hash_record); 92 | /* Zero the structure */ 93 | SyZero(pRecord,sizeof(mem_hash_record)); 94 | /* Fill in the structure */ 95 | pRecord->pEngine = pEngine; 96 | pRecord->nDataLen = (sxu32)nData; 97 | pRecord->nKeyLen = (sxu32)nKey; 98 | pRecord->nHash = nHash; 99 | SyMemcpy(pKey,zPtr,pRecord->nKeyLen); 100 | pRecord->pKey = (const void *)zPtr; 101 | SyMemcpy(pData,pDupData,pRecord->nDataLen); 102 | pRecord->pData = pDupData; 103 | /* All done */ 104 | return pRecord; 105 | } 106 | /* 107 | * Install a given record in the hashtable. 108 | */ 109 | static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord) 110 | { 111 | sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1); 112 | pRecord->pNextHash = pEngine->apBucket[nBucket]; 113 | if( pEngine->apBucket[nBucket] ){ 114 | pEngine->apBucket[nBucket]->pPrevHash = pRecord; 115 | } 116 | pEngine->apBucket[nBucket] = pRecord; 117 | if( pEngine->pFirst == 0 ){ 118 | pEngine->pFirst = pEngine->pLast = pRecord; 119 | }else{ 120 | MACRO_LD_PUSH(pEngine->pLast,pRecord); 121 | } 122 | pEngine->nRecord++; 123 | } 124 | /* 125 | * Unlink a given record from the hashtable. 126 | */ 127 | static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry) 128 | { 129 | sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1); 130 | SyMemBackend *pAlloc = &pEngine->sAlloc; 131 | if( pEntry->pPrevHash == 0 ){ 132 | pEngine->apBucket[nBucket] = pEntry->pNextHash; 133 | }else{ 134 | pEntry->pPrevHash->pNextHash = pEntry->pNextHash; 135 | } 136 | if( pEntry->pNextHash ){ 137 | pEntry->pNextHash->pPrevHash = pEntry->pPrevHash; 138 | } 139 | MACRO_LD_REMOVE(pEngine->pLast,pEntry); 140 | if( pEntry == pEngine->pFirst ){ 141 | pEngine->pFirst = pEntry->pPrev; 142 | } 143 | pEngine->nRecord--; 144 | /* Release the entry */ 145 | SyMemBackendFree(pAlloc,(void *)pEntry->pData); 146 | SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */ 147 | } 148 | /* 149 | * Perform a lookup for a given entry. 150 | */ 151 | static mem_hash_record * MemHashGetEntry( 152 | mem_hash_kv_engine *pEngine, 153 | const void *pKey,int nKeyLen 154 | ) 155 | { 156 | mem_hash_record *pEntry; 157 | sxu32 nHash,nBucket; 158 | /* Hash the entry */ 159 | nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); 160 | nBucket = nHash & (pEngine->nBucket - 1); 161 | pEntry = pEngine->apBucket[nBucket]; 162 | for(;;){ 163 | if( pEntry == 0 ){ 164 | break; 165 | } 166 | if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen && 167 | pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){ 168 | return pEntry; 169 | } 170 | pEntry = pEntry->pNextHash; 171 | } 172 | /* No such entry */ 173 | return 0; 174 | } 175 | /* 176 | * Rehash all the entries in the given table. 177 | */ 178 | static int MemHashGrowTable(mem_hash_kv_engine *pEngine) 179 | { 180 | sxu32 nNewSize = pEngine->nBucket << 1; 181 | mem_hash_record *pEntry; 182 | mem_hash_record **apNew; 183 | sxu32 n,iBucket; 184 | /* Allocate a new larger table */ 185 | apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *)); 186 | if( apNew == 0 ){ 187 | /* Not so fatal, simply a performance hit */ 188 | return UNQLITE_OK; 189 | } 190 | /* Zero the new table */ 191 | SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *)); 192 | /* Rehash all entries */ 193 | n = 0; 194 | pEntry = pEngine->pLast; 195 | for(;;){ 196 | 197 | /* Loop one */ 198 | if( n >= pEngine->nRecord ){ 199 | break; 200 | } 201 | pEntry->pNextHash = pEntry->pPrevHash = 0; 202 | /* Install in the new bucket */ 203 | iBucket = pEntry->nHash & (nNewSize - 1); 204 | pEntry->pNextHash = apNew[iBucket]; 205 | if( apNew[iBucket] ){ 206 | apNew[iBucket]->pPrevHash = pEntry; 207 | } 208 | apNew[iBucket] = pEntry; 209 | /* Point to the next entry */ 210 | pEntry = pEntry->pNext; 211 | n++; 212 | 213 | /* Loop two */ 214 | if( n >= pEngine->nRecord ){ 215 | break; 216 | } 217 | pEntry->pNextHash = pEntry->pPrevHash = 0; 218 | /* Install in the new bucket */ 219 | iBucket = pEntry->nHash & (nNewSize - 1); 220 | pEntry->pNextHash = apNew[iBucket]; 221 | if( apNew[iBucket] ){ 222 | apNew[iBucket]->pPrevHash = pEntry; 223 | } 224 | apNew[iBucket] = pEntry; 225 | /* Point to the next entry */ 226 | pEntry = pEntry->pNext; 227 | n++; 228 | 229 | /* Loop three */ 230 | if( n >= pEngine->nRecord ){ 231 | break; 232 | } 233 | pEntry->pNextHash = pEntry->pPrevHash = 0; 234 | /* Install in the new bucket */ 235 | iBucket = pEntry->nHash & (nNewSize - 1); 236 | pEntry->pNextHash = apNew[iBucket]; 237 | if( apNew[iBucket] ){ 238 | apNew[iBucket]->pPrevHash = pEntry; 239 | } 240 | apNew[iBucket] = pEntry; 241 | /* Point to the next entry */ 242 | pEntry = pEntry->pNext; 243 | n++; 244 | 245 | /* Loop four */ 246 | if( n >= pEngine->nRecord ){ 247 | break; 248 | } 249 | pEntry->pNextHash = pEntry->pPrevHash = 0; 250 | /* Install in the new bucket */ 251 | iBucket = pEntry->nHash & (nNewSize - 1); 252 | pEntry->pNextHash = apNew[iBucket]; 253 | if( apNew[iBucket] ){ 254 | apNew[iBucket]->pPrevHash = pEntry; 255 | } 256 | apNew[iBucket] = pEntry; 257 | /* Point to the next entry */ 258 | pEntry = pEntry->pNext; 259 | n++; 260 | } 261 | /* Release the old table and reflect the change */ 262 | SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket); 263 | pEngine->apBucket = apNew; 264 | pEngine->nBucket = nNewSize; 265 | return UNQLITE_OK; 266 | } 267 | /* 268 | * Exported Interfaces. 269 | */ 270 | /* 271 | * Each public cursor is identified by an instance of this structure. 272 | */ 273 | typedef struct mem_hash_cursor mem_hash_cursor; 274 | struct mem_hash_cursor 275 | { 276 | unqlite_kv_engine *pStore; /* Must be first */ 277 | /* Private fields */ 278 | mem_hash_record *pCur; /* Current hash record */ 279 | }; 280 | /* 281 | * Initialize the cursor. 282 | */ 283 | static void MemHashInitCursor(unqlite_kv_cursor *pCursor) 284 | { 285 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; 286 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 287 | /* Point to the first inserted entry */ 288 | pMem->pCur = pEngine->pFirst; 289 | } 290 | /* 291 | * Point to the first entry. 292 | */ 293 | static int MemHashCursorFirst(unqlite_kv_cursor *pCursor) 294 | { 295 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; 296 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 297 | pMem->pCur = pEngine->pFirst; 298 | return UNQLITE_OK; 299 | } 300 | /* 301 | * Point to the last entry. 302 | */ 303 | static int MemHashCursorLast(unqlite_kv_cursor *pCursor) 304 | { 305 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; 306 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 307 | pMem->pCur = pEngine->pLast; 308 | return UNQLITE_OK; 309 | } 310 | /* 311 | * is a Valid Cursor. 312 | */ 313 | static int MemHashCursorValid(unqlite_kv_cursor *pCursor) 314 | { 315 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 316 | return pMem->pCur != 0 ? 1 : 0; 317 | } 318 | /* 319 | * Point to the next entry. 320 | */ 321 | static int MemHashCursorNext(unqlite_kv_cursor *pCursor) 322 | { 323 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 324 | if( pMem->pCur == 0){ 325 | return UNQLITE_EOF; 326 | } 327 | pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */ 328 | return UNQLITE_OK; 329 | } 330 | /* 331 | * Point to the previous entry. 332 | */ 333 | static int MemHashCursorPrev(unqlite_kv_cursor *pCursor) 334 | { 335 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 336 | if( pMem->pCur == 0){ 337 | return UNQLITE_EOF; 338 | } 339 | pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */ 340 | return UNQLITE_OK; 341 | } 342 | /* 343 | * Return key length. 344 | */ 345 | static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) 346 | { 347 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 348 | if( pMem->pCur == 0){ 349 | return UNQLITE_EOF; 350 | } 351 | *pLen = (int)pMem->pCur->nKeyLen; 352 | return UNQLITE_OK; 353 | } 354 | /* 355 | * Return data length. 356 | */ 357 | static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) 358 | { 359 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 360 | if( pMem->pCur == 0 ){ 361 | return UNQLITE_EOF; 362 | } 363 | *pLen = pMem->pCur->nDataLen; 364 | return UNQLITE_OK; 365 | } 366 | /* 367 | * Consume the key. 368 | */ 369 | static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) 370 | { 371 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 372 | int rc; 373 | if( pMem->pCur == 0){ 374 | return UNQLITE_EOF; 375 | } 376 | /* Invoke the callback */ 377 | rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData); 378 | /* Callback result */ 379 | return rc; 380 | } 381 | /* 382 | * Consume the data. 383 | */ 384 | static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) 385 | { 386 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 387 | int rc; 388 | if( pMem->pCur == 0){ 389 | return UNQLITE_EOF; 390 | } 391 | /* Invoke the callback */ 392 | rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData); 393 | /* Callback result */ 394 | return rc; 395 | } 396 | /* 397 | * Reset the cursor. 398 | */ 399 | static void MemHashCursorReset(unqlite_kv_cursor *pCursor) 400 | { 401 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 402 | pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst; 403 | } 404 | /* 405 | * Remove a particular record. 406 | */ 407 | static int MemHashCursorDelete(unqlite_kv_cursor *pCursor) 408 | { 409 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 410 | mem_hash_record *pNext; 411 | if( pMem->pCur == 0 ){ 412 | /* Cursor does not point to anything */ 413 | return UNQLITE_NOTFOUND; 414 | } 415 | pNext = pMem->pCur->pPrev; 416 | /* Perform the deletion */ 417 | MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur); 418 | /* Point to the next entry */ 419 | pMem->pCur = pNext; 420 | return UNQLITE_OK; 421 | } 422 | /* 423 | * Find a particular record. 424 | */ 425 | static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) 426 | { 427 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; 428 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; 429 | /* Perform the lookup */ 430 | pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte); 431 | if( pMem->pCur == 0 ){ 432 | if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){ 433 | /* noop; */ 434 | } 435 | /* No such record */ 436 | return UNQLITE_NOTFOUND; 437 | } 438 | return UNQLITE_OK; 439 | } 440 | /* 441 | * Builtin hash function. 442 | */ 443 | static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen) 444 | { 445 | register unsigned char *zIn = (unsigned char *)pSrc; 446 | unsigned char *zEnd; 447 | sxu32 nH = 5381; 448 | zEnd = &zIn[nLen]; 449 | for(;;){ 450 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; 451 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; 452 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; 453 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; 454 | } 455 | return nH; 456 | } 457 | /* Default bucket size */ 458 | #define MEM_HASH_BUCKET_SIZE 64 459 | /* Default fill factor */ 460 | #define MEM_HASH_FILL_FACTOR 4 /* or 3 */ 461 | /* 462 | * Initialize the in-memory storage engine. 463 | */ 464 | static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize) 465 | { 466 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; 467 | /* Note that this instance is already zeroed */ 468 | /* Memory backend */ 469 | SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend()); 470 | //#if defined(UNQLITE_ENABLE_THREADS) 471 | // /* Already protected by the upper layers */ 472 | // SyMemBackendDisbaleMutexing(&pEngine->sAlloc); 473 | //#endif 474 | /* Default hash & comparison function */ 475 | pEngine->xHash = MemHashFunc; 476 | pEngine->xCmp = SyMemcmp; 477 | /* Allocate a new bucket */ 478 | pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); 479 | if( pEngine->apBucket == 0 ){ 480 | SXUNUSED(iPageSize); /* cc warning */ 481 | return UNQLITE_NOMEM; 482 | } 483 | /* Zero the bucket */ 484 | SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); 485 | pEngine->nRecord = 0; 486 | pEngine->nBucket = MEM_HASH_BUCKET_SIZE; 487 | return UNQLITE_OK; 488 | } 489 | /* 490 | * Release the in-memory storage engine. 491 | */ 492 | static void MemHashRelease(unqlite_kv_engine *pKvEngine) 493 | { 494 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; 495 | /* Release the private memory backend */ 496 | SyMemBackendRelease(&pEngine->sAlloc); 497 | } 498 | /* 499 | * Configure the in-memory storage engine. 500 | */ 501 | static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap) 502 | { 503 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; 504 | int rc = UNQLITE_OK; 505 | switch(iOp){ 506 | case UNQLITE_KV_CONFIG_HASH_FUNC:{ 507 | /* Use a default hash function */ 508 | if( pEngine->nRecord > 0 ){ 509 | rc = UNQLITE_LOCKED; 510 | }else{ 511 | ProcHash xHash = va_arg(ap,ProcHash); 512 | if( xHash ){ 513 | pEngine->xHash = xHash; 514 | } 515 | } 516 | break; 517 | } 518 | case UNQLITE_KV_CONFIG_CMP_FUNC: { 519 | /* Default comparison function */ 520 | ProcCmp xCmp = va_arg(ap,ProcCmp); 521 | if( xCmp ){ 522 | pEngine->xCmp = xCmp; 523 | } 524 | break; 525 | } 526 | default: 527 | /* Unknown configuration option */ 528 | rc = UNQLITE_UNKNOWN; 529 | } 530 | return rc; 531 | } 532 | /* 533 | * Replace method. 534 | */ 535 | static int MemHashReplace( 536 | unqlite_kv_engine *pKv, 537 | const void *pKey,int nKeyLen, 538 | const void *pData,unqlite_int64 nDataLen 539 | ) 540 | { 541 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; 542 | mem_hash_record *pRecord; 543 | if( nDataLen > SXU32_HIGH ){ 544 | /* Database limit */ 545 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); 546 | return UNQLITE_LIMIT; 547 | } 548 | /* Fetch the record first */ 549 | pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); 550 | if( pRecord == 0 ){ 551 | /* Allocate a new record */ 552 | pRecord = MemHashNewRecord(pEngine, 553 | pKey,nKeyLen, 554 | pData,nDataLen, 555 | pEngine->xHash(pKey,nKeyLen) 556 | ); 557 | if( pRecord == 0 ){ 558 | return UNQLITE_NOMEM; 559 | } 560 | /* Link the entry */ 561 | MemHashLinkRecord(pEngine,pRecord); 562 | if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){ 563 | /* Rehash the table */ 564 | MemHashGrowTable(pEngine); 565 | } 566 | }else{ 567 | sxu32 nData = (sxu32)nDataLen; 568 | void *pNew; 569 | /* Replace an existing record */ 570 | if( nData == pRecord->nDataLen ){ 571 | /* No need to free the old chunk */ 572 | pNew = (void *)pRecord->pData; 573 | }else{ 574 | pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData); 575 | if( pNew == 0 ){ 576 | return UNQLITE_NOMEM; 577 | } 578 | /* Release the old data */ 579 | SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData); 580 | } 581 | /* Reflect the change */ 582 | pRecord->nDataLen = nData; 583 | SyMemcpy(pData,pNew,nData); 584 | pRecord->pData = pNew; 585 | } 586 | return UNQLITE_OK; 587 | } 588 | /* 589 | * Append method. 590 | */ 591 | static int MemHashAppend( 592 | unqlite_kv_engine *pKv, 593 | const void *pKey,int nKeyLen, 594 | const void *pData,unqlite_int64 nDataLen 595 | ) 596 | { 597 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; 598 | mem_hash_record *pRecord; 599 | if( nDataLen > SXU32_HIGH ){ 600 | /* Database limit */ 601 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); 602 | return UNQLITE_LIMIT; 603 | } 604 | /* Fetch the record first */ 605 | pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); 606 | if( pRecord == 0 ){ 607 | /* Allocate a new record */ 608 | pRecord = MemHashNewRecord(pEngine, 609 | pKey,nKeyLen, 610 | pData,nDataLen, 611 | pEngine->xHash(pKey,nKeyLen) 612 | ); 613 | if( pRecord == 0 ){ 614 | return UNQLITE_NOMEM; 615 | } 616 | /* Link the entry */ 617 | MemHashLinkRecord(pEngine,pRecord); 618 | if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){ 619 | /* Rehash the table */ 620 | MemHashGrowTable(pEngine); 621 | } 622 | }else{ 623 | unqlite_int64 nNew = pRecord->nDataLen + nDataLen; 624 | void *pOld = (void *)pRecord->pData; 625 | sxu32 nData; 626 | char *zNew; 627 | /* Append data to the existing record */ 628 | if( nNew > SXU32_HIGH ){ 629 | /* Overflow */ 630 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); 631 | return UNQLITE_LIMIT; 632 | } 633 | nData = (sxu32)nNew; 634 | /* Allocate bigger chunk */ 635 | zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData); 636 | if( zNew == 0 ){ 637 | return UNQLITE_NOMEM; 638 | } 639 | /* Reflect the change */ 640 | SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen); 641 | pRecord->pData = (const void *)zNew; 642 | pRecord->nDataLen = nData; 643 | } 644 | return UNQLITE_OK; 645 | } 646 | /* 647 | * Export the in-memory storage engine. 648 | */ 649 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void) 650 | { 651 | static const unqlite_kv_methods sMemStore = { 652 | "mem", /* zName */ 653 | sizeof(mem_hash_kv_engine), /* szKv */ 654 | sizeof(mem_hash_cursor), /* szCursor */ 655 | 1, /* iVersion */ 656 | MemHashInit, /* xInit */ 657 | MemHashRelease, /* xRelease */ 658 | MemHashConfigure, /* xConfig */ 659 | 0, /* xOpen */ 660 | MemHashReplace, /* xReplace */ 661 | MemHashAppend, /* xAppend */ 662 | MemHashInitCursor, /* xCursorInit */ 663 | MemHashCursorSeek, /* xSeek */ 664 | MemHashCursorFirst, /* xFirst */ 665 | MemHashCursorLast, /* xLast */ 666 | MemHashCursorValid, /* xValid */ 667 | MemHashCursorNext, /* xNext */ 668 | MemHashCursorPrev, /* xPrev */ 669 | MemHashCursorDelete, /* xDelete */ 670 | MemHashCursorKeyLength, /* xKeyLength */ 671 | MemHashCursorKey, /* xKey */ 672 | MemHashCursorDataLength, /* xDataLength */ 673 | MemHashCursorData, /* xData */ 674 | MemHashCursorReset, /* xReset */ 675 | 0 /* xRelease */ 676 | }; 677 | return &sMemStore; 678 | } 679 | -------------------------------------------------------------------------------- /example/5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile this file together with the UnQLite database engine source code 3 | * to generate the executable. For example: 4 | * gcc -W -Wall -O6 unqlite_func_intro.c unqlite.c -o unqlite_unqlite_func 5 | */ 6 | /* 7 | * This simple program is a quick introduction on how to embed and start 8 | * experimenting with UnQLite without having to do a lot of tedious 9 | * reading and configuration. 10 | * 11 | * Introduction to the UnQLite (Via Jx9) Foreign Function Mechanism: 12 | * 13 | * The Document store to UnQLite which is used to store JSON docs (i.e. Objects, Arrays, Strings, etc.) 14 | * in the database is powered by the Jx9 programming language. 15 | * 16 | * Jx9 is an embeddable scripting language also called extension language designed 17 | * to support general procedural programming with data description facilities. 18 | * Jx9 is a Turing-Complete, dynamically typed programming language based on JSON 19 | * and implemented as a library in the UnQLite core. 20 | * 21 | * Jx9 is built with a tons of features and has a clean and familiar syntax similar 22 | * to C and Javascript. 23 | * Being an extension language, Jx9 has no notion of a main program, it only works 24 | * embedded in a host application. 25 | * The host program (UnQLite in our case) can write and read Jx9 variables and can 26 | * register C/C++ functions to be called by Jx9 code. 27 | * 28 | * Foreign functions are used to add Jx9 functions or to redefine the behavior of existing 29 | * Jx9 functions from the outside environment (see below) to the underlying virtual machine. 30 | * This mechanism is know as "In-process extending". After successful call to [unqlite_create_function()], 31 | * the installed function is available immediately and can be called from the target Jx9 code. 32 | * 33 | * For an introductory course to the [unqlite_create_function()] interface and the foreign 34 | * function mechanism in general, please refer to: 35 | * http://unqlite.org/func_intro.html 36 | * For an introduction to the UnQLite C/C++ interface, please refer to: 37 | * http://unqlite.org/api_intro.html 38 | * For an introduction to Jx9, please refer to: 39 | * http://unqlite.org/unqlite.html 40 | * For the full C/C++ API reference guide, please refer to: 41 | * http://unqlite.org/c_api.html 42 | * UnQLite in 5 Minutes or Less: 43 | * http://unqlite.org/intro.html 44 | * The Architecture of the UnQLite Database Engine: 45 | * http://unqlite.org/arch.html 46 | */ 47 | /* $SymiscID: unqlite_func_intro.c v1.7 Linux 2013-05-17 03:17 stable $ */ 48 | /* 49 | * Make sure you have the latest release of UnQLite from: 50 | * http://unqlite.org/downloads.html 51 | */ 52 | #include /* puts() */ 53 | #include /* exit() */ 54 | /* Make sure this header file is available.*/ 55 | #include "unqlite.h" 56 | /* 57 | * Banner. 58 | */ 59 | static const char zBanner[] = { 60 | "============================================================\n" 61 | "UnQLite (Via Jx9) Foreign Functions \n" 62 | " http://unqlite.org/\n" 63 | "============================================================\n" 64 | }; 65 | /* 66 | * Extract the database error log and exit. 67 | */ 68 | static void Fatal(unqlite *pDb,const char *zMsg) 69 | { 70 | if( pDb ){ 71 | const char *zErr; 72 | int iLen = 0; /* Stupid cc warning */ 73 | 74 | /* Extract the database error log */ 75 | unqlite_config(pDb,UNQLITE_CONFIG_ERR_LOG,&zErr,&iLen); 76 | if( iLen > 0 ){ 77 | /* Output the DB error log */ 78 | puts(zErr); /* Always null terminated */ 79 | } 80 | }else{ 81 | if( zMsg ){ 82 | puts(zMsg); 83 | } 84 | } 85 | /* Manually shutdown the library */ 86 | unqlite_lib_shutdown(); 87 | /* Exit immediately */ 88 | exit(0); 89 | } 90 | /* 91 | * int shift_func(int $num) 92 | * Right shift a number by one and return the result. 93 | * Description 94 | * Our first function perform a simple right shift operation on a given number 95 | * and return that number shifted by one. 96 | * This function expects a single parameter which must be numeric (either integer or float 97 | * or a string that looks like a number). 98 | * Parameter 99 | * $num 100 | * Number to shift by one. 101 | * Return value 102 | * Integer: Given number shifted by one. 103 | * Usage example: 104 | * print shift_func(150); //Output 300 105 | * print shift_func(50); //Output 100 106 | */ 107 | int shift_func( 108 | unqlite_context *pCtx, /* Call Context */ 109 | int argc, /* Total number of arguments passed to the function */ 110 | unqlite_value **argv /* Array of function arguments */ 111 | ) 112 | { 113 | int num; 114 | /* Make sure there is at least one argument and is of the 115 | * expected type [i.e: numeric]. 116 | */ 117 | if( argc < 1 || !unqlite_value_is_numeric(argv[0]) ){ 118 | /* 119 | * Missing/Invalid argument, throw a warning and return FALSE. 120 | * Note that you do not need to log the function name, UnQLite will 121 | * automatically append the function name for you. 122 | */ 123 | unqlite_context_throw_error(pCtx, UNQLITE_CTX_WARNING, "Missing numeric argument"); 124 | /* Return false */ 125 | unqlite_result_bool(pCtx, 0); 126 | return UNQLITE_OK; 127 | } 128 | /* Extract the number */ 129 | num = unqlite_value_to_int(argv[0]); 130 | /* Shift by 1 */ 131 | num <<= 1; 132 | /* Return the new value */ 133 | unqlite_result_int(pCtx, num); 134 | /* All done */ 135 | return UNQLITE_OK; 136 | } 137 | #include 138 | /* 139 | * string date_func(void) 140 | * Return the current system date. 141 | * Description 142 | * Our second function does not expects arguments and return the 143 | * current system date. 144 | * Parameter 145 | * None 146 | * Return value 147 | * String: Current system date. 148 | * Usage example 149 | * print date_func(); //would output: 2012-23-09 14:53:30 150 | */ 151 | int date_func( 152 | unqlite_context *pCtx, /* Call Context */ 153 | int argc, /* Total number of arguments passed to the function */ 154 | unqlite_value **argv /* Array of function arguments*/ 155 | ){ 156 | struct tm *pNow; 157 | #if defined(_MSC_VER) || defined(__MINGW32__) 158 | errno_t err; 159 | __time64_t long_time; 160 | struct tm newtime; 161 | _time64( &long_time ); 162 | err = _localtime64_s(&newtime, &long_time); 163 | if (err != 0) return UNQLITE_INVALID; 164 | pNow = &newtime; 165 | #else 166 | time_t tt; 167 | /* Get the current time */ 168 | time(&tt); 169 | pNow = localtime(&tt); 170 | #endif 171 | /* 172 | * Return the current date. 173 | */ 174 | unqlite_result_string_format(pCtx, 175 | "%04d-%02d-%02d %02d:%02d:%02d", /* printf() style format */ 176 | pNow->tm_year + 1900, /* Year */ 177 | pNow->tm_mday, /* Day of the month */ 178 | pNow->tm_mon + 1, /* Month number */ 179 | pNow->tm_hour, /* Hour */ 180 | pNow->tm_min, /* Minutes */ 181 | pNow->tm_sec /* Seconds */ 182 | ); 183 | 184 | /* All done */ 185 | return UNQLITE_OK; 186 | } 187 | /* 188 | * int64 sum_func(int $arg1, int $arg2, int $arg3, ...) 189 | * Return the sum of the given arguments. 190 | * Description 191 | * This function expects a variable number of arguments which must be of type 192 | * numeric (either integer or float or a string that looks like a number) and 193 | * returns the sum of the given numbers. 194 | * Parameter 195 | * int $n1, n2, ... (Variable number of arguments) 196 | * Return value 197 | * Integer64: Sum of the given numbers. 198 | * Usage example 199 | * print sum_func(7, 8, 9, 10); //would output 34 200 | */ 201 | int sum_func(unqlite_context *pCtx, int argc, unqlite_value **argv) 202 | { 203 | unqlite_int64 iTotal = 0; /* Counter */ 204 | int i; 205 | if( argc < 1 ){ 206 | /* 207 | * Missing arguments, throw a notice and return NULL. 208 | * Note that you do not need to log the function name, UnQLite will 209 | * automatically append the function name for you. 210 | */ 211 | unqlite_context_throw_error(pCtx, UNQLITE_CTX_NOTICE, "Missing function arguments $arg1, $arg2, .."); 212 | /* Return null */ 213 | unqlite_result_null(pCtx); 214 | return UNQLITE_OK; 215 | } 216 | /* Sum the arguments */ 217 | for( i = 0; i < argc ; i++ ){ 218 | unqlite_value *pVal = argv[i]; 219 | unqlite_int64 n; 220 | /* Make sure we are dealing with a numeric argument */ 221 | if( !unqlite_value_is_numeric(pVal) ){ 222 | /* Throw a notice and continue */ 223 | unqlite_context_throw_error_format(pCtx, UNQLITE_CTX_NOTICE, 224 | "Arg[%d]: Expecting a numeric value", /* printf() style format */ 225 | i 226 | ); 227 | /* Ignore */ 228 | continue; 229 | } 230 | /* Get a 64-bit integer representation and increment the counter */ 231 | n = unqlite_value_to_int64(pVal); 232 | iTotal += n; 233 | } 234 | /* Return the count */ 235 | unqlite_result_int64(pCtx, iTotal); 236 | /* All done */ 237 | return UNQLITE_OK; 238 | } 239 | /* 240 | * array array_time_func(void) 241 | * Return the current system time in a JSON array. 242 | * Description 243 | * This function does not expects arguments and return the 244 | * current system time in an array. 245 | * Parameter 246 | * None 247 | * Return value 248 | * Array holding the current system time. 249 | * Usage example 250 | * 251 | * print array_time_func() ; 252 | * 253 | * When running you should see something like that: 254 | * JSON array(3) [14,53,30] 255 | */ 256 | int array_time_func(unqlite_context *pCtx, int argc, unqlite_value **argv) 257 | { 258 | unqlite_value *pArray; /* Our JSON Array */ 259 | unqlite_value *pValue; /* Array entries value */ 260 | struct tm *pNow; 261 | /* Get the current time first */ 262 | #if defined(_MSC_VER) || defined(__MINGW32__) 263 | errno_t err; 264 | __time64_t long_time; 265 | struct tm newtime; 266 | _time64( &long_time ); 267 | err = _localtime64_s(&newtime, &long_time); 268 | if (err != 0) return UNQLITE_INVALID; 269 | pNow = &newtime; 270 | #else 271 | time_t tt; 272 | time(&tt); 273 | pNow = localtime(&tt); 274 | #endif 275 | /* Create a new array */ 276 | pArray = unqlite_context_new_array(pCtx); 277 | /* Create a worker scalar value */ 278 | pValue = unqlite_context_new_scalar(pCtx); 279 | if( pArray == 0 || pValue == 0 ){ 280 | /* 281 | * If the supplied memory subsystem is so sick that we are unable 282 | * to allocate a tiny chunk of memory, there is no much we can do here. 283 | * Abort immediately. 284 | */ 285 | unqlite_context_throw_error(pCtx, UNQLITE_CTX_ERR, "Fatal, out of memory"); 286 | /* emulate the die() construct */ 287 | return UNQLITE_ABORT; 288 | } 289 | /* Populate the array. 290 | * Note that we will use the same worker scalar value (pValue) here rather than 291 | * allocating a new value for each array entry. This is due to the fact 292 | * that the populated array will make it's own private copy of the inserted 293 | * key(if available) and it's associated value. 294 | */ 295 | 296 | unqlite_value_int(pValue, pNow->tm_hour); /* Hour */ 297 | /* Insert the hour at the first available index */ 298 | unqlite_array_add_elem(pArray, 0/* NULL: Assign an automatic index*/, pValue /* Will make it's own copy */); 299 | 300 | /* Overwrite the previous value */ 301 | unqlite_value_int(pValue, pNow->tm_min); /* Minutes */ 302 | /* Insert minutes */ 303 | unqlite_array_add_elem(pArray, 0/* NULL: Assign an automatic index*/, pValue /* Will make it's own copy */); 304 | 305 | /* Overwrite the previous value */ 306 | unqlite_value_int(pValue, pNow->tm_sec); /* Seconds */ 307 | /* Insert seconds */ 308 | unqlite_array_add_elem(pArray, 0/* NULL: Assign an automatic index*/, pValue /* Will make it's own copy */); 309 | 310 | /* Return the array as the function return value */ 311 | unqlite_result_value(pCtx, pArray); 312 | 313 | /* All done. Don't worry about freeing memory here, every 314 | * allocated resource will be released automatically by the engine 315 | * as soon we return from this foreign function. 316 | */ 317 | return UNQLITE_OK; 318 | } 319 | /* 320 | * object object_date_func(void) 321 | * Return a copy of the 'struct tm' structure in a JSON array. 322 | * Description 323 | * This function does not expects arguments and return a copy of 324 | * the 'struct tm' structure found in the 'time.h' header file. 325 | * This structure hold the current system date&time. 326 | * Parameter 327 | * None 328 | * Return value 329 | * Associative array holding a copy of the 'struct tm' structure. 330 | * Usage example 331 | * 332 | * print object_date_func(); 333 | * 334 | * When running you should see something like that: 335 | * JSON Object(6 { 336 | * "tm_year":2012, 337 | * "tm_mon":12, 338 | * "tm_mday":29, 339 | * "tm_hour":1, 340 | * "tm_min":13, 341 | * "tm_sec":58 342 | * } 343 | * ) 344 | */ 345 | int object_date_func(unqlite_context *pCtx, int argc /* unused */, unqlite_value **argv /* unused */) 346 | { 347 | unqlite_value *pObject; /* Our JSON object */ 348 | unqlite_value *pValue; /* Objecr entries value */ 349 | struct tm *pNow; 350 | /* Get the current time first */ 351 | #if defined(_MSC_VER) || defined(__MINGW32__) 352 | errno_t err; 353 | __time64_t long_time; 354 | struct tm newtime; 355 | _time64( &long_time ); 356 | err = _localtime64_s(&newtime, &long_time); 357 | if (err != 0) return UNQLITE_INVALID; 358 | pNow = &newtime; 359 | #else 360 | time_t tt; 361 | time(&tt); 362 | pNow = localtime(&tt); 363 | #endif 364 | /* Create a new JSON object */ 365 | pObject = unqlite_context_new_array(pCtx); 366 | /* Create a worker scalar value */ 367 | pValue = unqlite_context_new_scalar(pCtx); 368 | if( pObject == 0 || pValue == 0 ){ 369 | /* 370 | * If the supplied memory subsystem is so sick that we are unable 371 | * to allocate a tiny chunk of memory, there is no much we can do here. 372 | * Abort immediately. 373 | */ 374 | unqlite_context_throw_error(pCtx, UNQLITE_CTX_ERR, "Fatal, out of memory"); 375 | /* emulate the die() construct */ 376 | return UNQLITE_ABORT; 377 | } 378 | /* Populate the array. 379 | * Note that we will use the same worker scalar value (pValue) here rather than 380 | * allocating a new value for each array entry. This is due to the fact 381 | * that the populated array will make it's own private copy of the inserted 382 | * key(if available) and it's associated value. 383 | */ 384 | 385 | unqlite_value_int(pValue, pNow->tm_year + 1900); /* Year */ 386 | /* Insert Year */ 387 | unqlite_array_add_strkey_elem(pObject, "tm_year", pValue /* Will make it's own copy */); 388 | 389 | /* Overwrite the previous value */ 390 | unqlite_value_int(pValue, pNow->tm_mon + 1); /* Month [1-12]*/ 391 | /* Insert month number */ 392 | unqlite_array_add_strkey_elem(pObject, "tm_mon", pValue /* Will make it's own copy */); 393 | 394 | /* Overwrite the previous value */ 395 | unqlite_value_int(pValue, pNow->tm_mday); /* Day of the month [1-31] */ 396 | /* Insert the day of the month */ 397 | unqlite_array_add_strkey_elem(pObject, "tm_mday", pValue /* Will make it's own copy */); 398 | 399 | unqlite_value_int(pValue, pNow->tm_hour); /* Hour */ 400 | /* Insert the hour */ 401 | unqlite_array_add_strkey_elem(pObject, "tm_hour", pValue /* Will make it's own copy */); 402 | 403 | /* Overwrite the previous value */ 404 | unqlite_value_int(pValue, pNow->tm_min); /* Minutes */ 405 | /* Insert minutes */ 406 | unqlite_array_add_strkey_elem(pObject, "tm_min", pValue /* Will make it's own copy */); 407 | 408 | /* Overwrite the previous value */ 409 | unqlite_value_int(pValue, pNow->tm_sec); /* Seconds */ 410 | /* Insert seconds */ 411 | unqlite_array_add_strkey_elem(pObject, "tm_sec", pValue /* Will make it's own copy */); 412 | 413 | /* Return the JSON object as the function return value */ 414 | unqlite_result_value(pCtx, pObject); 415 | /* All done. Don't worry about freeing memory here, every 416 | * allocated resource will be released automatically by the engine 417 | * as soon we return from this foreign function. 418 | */ 419 | return UNQLITE_OK; 420 | } 421 | /* 422 | * array array_string_split(string $str) 423 | * Return a copy of each string character in an array. 424 | * Description 425 | * This function splits a given string to its 426 | * characters and return the result in an array. 427 | * Parameter 428 | * $str 429 | * Target string to split. 430 | * Return value 431 | * Array holding string characters. 432 | * Usage example 433 | * 434 | * print array_str_split('Hello'); 435 | * 436 | * When running you should see something like that: 437 | * JSON Array(5 ["H","e","l","l","o"]) 438 | */ 439 | int array_string_split_func(unqlite_context *pCtx, int argc, unqlite_value **argv) 440 | { 441 | unqlite_value *pArray; /* Our JSON Array */ 442 | unqlite_value *pValue; /* Array entries value */ 443 | const char *zString, *zEnd; /* String to split */ 444 | int nByte; /* String length */ 445 | /* Make sure there is at least one argument and is of the 446 | * expected type [i.e: string]. 447 | */ 448 | if( argc < 1 || !unqlite_value_is_string(argv[0]) ){ 449 | /* 450 | * Missing/Invalid argument, throw a warning and return FALSE. 451 | * Note that you do not need to log the function name, UnQLite will 452 | * automatically append the function name for you. 453 | */ 454 | unqlite_context_throw_error(pCtx, UNQLITE_CTX_WARNING, "Missing string to split"); 455 | /* Return false */ 456 | unqlite_result_bool(pCtx, 0); 457 | return UNQLITE_OK; 458 | } 459 | /* Extract the target string. 460 | * Note that zString is null terminated and unqlite_value_to_string() never 461 | * fail and always return a pointer to a null terminated string. 462 | */ 463 | zString = unqlite_value_to_string(argv[0], &nByte /* String length */); 464 | if( nByte < 1 /* Empty string [i.e: '' or ""] */ ){ 465 | unqlite_context_throw_error(pCtx, UNQLITE_CTX_NOTICE, "Empty string"); 466 | /* Return false */ 467 | unqlite_result_bool(pCtx, 0); 468 | return UNQLITE_OK; 469 | } 470 | /* Create our array */ 471 | pArray = unqlite_context_new_array(pCtx); 472 | /* Create a scalar worker value */ 473 | pValue = unqlite_context_new_scalar(pCtx); 474 | /* Split the target string */ 475 | zEnd = &zString[nByte]; /* Delimit the string */ 476 | while( zString < zEnd ){ 477 | int c = zString[0]; 478 | /* Prepare the character for insertion */ 479 | unqlite_value_string(pValue, (const char *)&c, (int)sizeof(char)); 480 | /* Insert the character */ 481 | unqlite_array_add_elem(pArray, 0/* NULL: Assign an automatic index */, pValue /* Will make it's own copy*/); 482 | /* Erase the previous data from the worker variable */ 483 | unqlite_value_reset_string_cursor(pValue); 484 | /* Next character */ 485 | zString++; 486 | } 487 | /* Return our array as the function return value */ 488 | unqlite_result_value(pCtx, pArray); 489 | /* All done. Don't worry about freeing memory here, every 490 | * allocated resource will be released automatically by the engine 491 | * as soon we return from this foreign function. 492 | */ 493 | return UNQLITE_OK; 494 | } 495 | /* 496 | * Container for the foreign functions defined above. 497 | * These functions will be registered later using a call 498 | * to [unqlite_create_function()]. 499 | */ 500 | static const struct foreign_func { 501 | const char *zName; /* Name of the foreign function*/ 502 | int (*xProc)(unqlite_context *, int, unqlite_value **); /* Pointer to the C function performing the computation*/ 503 | }aFunc[] = { 504 | {"shift_func", shift_func}, 505 | {"date_func", date_func}, 506 | {"sum_func", sum_func }, 507 | {"array_time_func", array_time_func}, 508 | {"array_str_split", array_string_split_func}, 509 | {"object_date_func", object_date_func} 510 | }; 511 | /* Forward declaration: VM output consumer callback */ 512 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */); 513 | /* 514 | * The following is the Jx9 Program to be executed later by the UnQLite VM: 515 | * 516 | * //Test the foreign function mechanism 517 | * print 'shift_func(150) = ' .. shift_func(150) .. JX9_EOL; 518 | * print 'sum_func(7,8,9,10) = ' .. sum_func(7,8,9,10) .. JX9_EOL; 519 | * print 'date_func(5) = ' .. date_func() .. JX9_EOL; 520 | * print 'array_time_func() =' .. array_time_func() .. JX9_EOL; 521 | * print 'object_date_func() =' .. JX9_EOL; 522 | * dump(object_date_func()); 523 | * print 'array_str_split('Hello') =' .. JX9_EOL; 524 | * dump(array_str_split('Hello')) 525 | * 526 | * When running, you should see something like that: 527 | * 528 | * shift_func(150) = 300 529 | * sum_func(7,8,9,10) = 34 530 | * date_func(5) = 2013-06-12 01:13:58 531 | * array_time_func() = [1,13,58] 532 | * object_date_func() = 533 | * JSON Object(6 { 534 | * "tm_year":2012, 535 | * "tm_mon":12, 536 | * "tm_mday":29, 537 | * "tm_hour":1, 538 | * "tm_min":13, 539 | * "tm_sec":58 540 | * } 541 | * ) 542 | * array_str_split('Hello') = 543 | * JSON Array(5 ["H","e","l","l","o"]) 544 | * 545 | * Note: '..' (Two dots) is the concatenation operator (i.e '+' for Javascript) 546 | */ 547 | #define JX9_PROG \ 548 | "print 'shift_func(150) = ' .. shift_func(150) .. JX9_EOL;"\ 549 | "print 'sum_func(7,8,9,10) = ' .. sum_func(7,8,9,10) .. JX9_EOL;"\ 550 | "print 'date_func(5) = ' .. date_func() .. JX9_EOL;"\ 551 | "print 'array_time_func() =' .. array_time_func() .. JX9_EOL;"\ 552 | "print 'object_date_func() =' ..JX9_EOL;"\ 553 | "dump(object_date_func());"\ 554 | "print 'array_str_split(\\'Hello\\') =' .. JX9_EOL;"\ 555 | "dump(array_str_split('Hello'));" 556 | 557 | /* No need for command line arguments, everything is stored in-memory */ 558 | int main(void) 559 | { 560 | unqlite *pDb; /* Database handle */ 561 | unqlite_vm *pVm; /* UnQLite VM resulting from successful compilation of the target Jx9 script */ 562 | int i,rc; 563 | 564 | puts(zBanner); 565 | 566 | /* Open our database */ 567 | rc = unqlite_open(&pDb,":mem:" /* In-mem DB */,UNQLITE_OPEN_CREATE); 568 | if( rc != UNQLITE_OK ){ 569 | Fatal(0,"Out of memory"); 570 | } 571 | 572 | /* Compile our Jx9 script defined above */ 573 | rc = unqlite_compile(pDb,JX9_PROG,sizeof(JX9_PROG)-1,&pVm); 574 | if( rc != UNQLITE_OK ){ 575 | /* Compile error, extract the compiler error log */ 576 | const char *zBuf; 577 | int iLen; 578 | /* Extract error log */ 579 | unqlite_config(pDb,UNQLITE_CONFIG_JX9_ERR_LOG,&zBuf,&iLen); 580 | if( iLen > 0 ){ 581 | puts(zBuf); 582 | } 583 | Fatal(0,"Jx9 compile error"); 584 | } 585 | 586 | /* Now we have our program compiled, it's time to register 587 | * our foreign functions. 588 | */ 589 | for( i = 0 ; i < (int)sizeof(aFunc)/sizeof(aFunc[0]) ; ++i ){ 590 | /* Install the foreign function */ 591 | rc = unqlite_create_function(pVm, aFunc[i].zName, aFunc[i].xProc, 0 /* NULL: No private data */); 592 | if( rc != UNQLITE_OK ){ 593 | Fatal(pDb,"Error while registering foreign functions"); 594 | } 595 | } 596 | 597 | /* Install a VM output consumer callback */ 598 | rc = unqlite_vm_config(pVm,UNQLITE_VM_CONFIG_OUTPUT,VmOutputConsumer,0); 599 | if( rc != UNQLITE_OK ){ 600 | Fatal(pDb,0); 601 | } 602 | 603 | /* Execute our script */ 604 | rc = unqlite_vm_exec(pVm); 605 | if( rc != UNQLITE_OK ){ 606 | Fatal(pDb,0); 607 | } 608 | 609 | /* Release our VM */ 610 | unqlite_vm_release(pVm); 611 | 612 | /* Auto-commit the transaction and close our database */ 613 | unqlite_close(pDb); 614 | return 0; 615 | } 616 | 617 | #ifdef __WINNT__ 618 | #include 619 | #else 620 | /* Assume UNIX */ 621 | #include 622 | #endif 623 | /* 624 | * The following define is used by the UNIX build process and have 625 | * no particular meaning on windows. 626 | */ 627 | #ifndef STDOUT_FILENO 628 | #define STDOUT_FILENO 1 629 | #endif 630 | /* 631 | * VM output consumer callback. 632 | * Each time the UnQLite VM generates some outputs, the following 633 | * function gets called by the underlying virtual machine to consume 634 | * the generated output. 635 | * 636 | * All this function does is redirecting the VM output to STDOUT. 637 | * This function is registered via a call to [unqlite_vm_config()] 638 | * with a configuration verb set to: UNQLITE_VM_CONFIG_OUTPUT. 639 | */ 640 | static int VmOutputConsumer(const void *pOutput,unsigned int nOutLen,void *pUserData /* Unused */) 641 | { 642 | #ifdef __WINNT__ 643 | BOOL rc; 644 | rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),pOutput,(DWORD)nOutLen,0,0); 645 | if( !rc ){ 646 | /* Abort processing */ 647 | return UNQLITE_ABORT; 648 | } 649 | #else 650 | ssize_t nWr; 651 | nWr = write(STDOUT_FILENO,pOutput,nOutLen); 652 | if( nWr < 0 ){ 653 | /* Abort processing */ 654 | return UNQLITE_ABORT; 655 | } 656 | #endif /* __WINT__ */ 657 | 658 | /* All done, data was redirected to STDOUT */ 659 | return UNQLITE_OK; 660 | } 661 | -------------------------------------------------------------------------------- /src/jx9_json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. 3 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ 4 | * Version 1.7.2 5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES 6 | * please contact Symisc Systems via: 7 | * legal@symisc.net 8 | * licensing@symisc.net 9 | * contact@symisc.net 10 | * or visit: 11 | * http://jx9.symisc.net/ 12 | */ 13 | /* $SymiscID: json.c v1.0 FreeBSD 2012-12-16 00:28 stable $ */ 14 | #ifndef JX9_AMALGAMATION 15 | #include "jx9Int.h" 16 | #endif 17 | /* This file deals with JSON serialization, decoding and stuff like that. */ 18 | /* 19 | * Section: 20 | * JSON encoding/decoding routines. 21 | * Authors: 22 | * Symisc Systems, devel@symisc.net. 23 | * Copyright (C) Symisc Systems, http://jx9.symisc.net 24 | * Status: 25 | * Devel. 26 | */ 27 | /* Forward reference */ 28 | static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); 29 | static int VmJsonObjectEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); 30 | /* 31 | * JSON encoder state is stored in an instance 32 | * of the following structure. 33 | */ 34 | typedef struct json_private_data json_private_data; 35 | struct json_private_data 36 | { 37 | SyBlob *pOut; /* Output consumer buffer */ 38 | int isFirst; /* True if first encoded entry */ 39 | int iFlags; /* JSON encoding flags */ 40 | int nRecCount; /* Recursion count */ 41 | }; 42 | /* 43 | * Returns the JSON representation of a value.In other word perform a JSON encoding operation. 44 | * According to wikipedia 45 | * JSON's basic types are: 46 | * Number (double precision floating-point format in JavaScript, generally depends on implementation) 47 | * String (double-quoted Unicode, with backslash escaping) 48 | * Boolean (true or false) 49 | * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values 50 | * do not need to be of the same type) 51 | * Object (an unordered collection of key:value pairs with the ':' character separating the key 52 | * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should 53 | * be distinct from each other) 54 | * null (empty) 55 | * Non-significant white space may be added freely around the "structural characters" 56 | * (i.e. the brackets "[{]}", colon ":" and comma ","). 57 | */ 58 | static sxi32 VmJsonEncode( 59 | jx9_value *pIn, /* Encode this value */ 60 | json_private_data *pData /* Context data */ 61 | ){ 62 | SyBlob *pOut = pData->pOut; 63 | int nByte; 64 | if( jx9_value_is_null(pIn) || jx9_value_is_resource(pIn)){ 65 | /* null */ 66 | SyBlobAppend(pOut, "null", sizeof("null")-1); 67 | }else if( jx9_value_is_bool(pIn) ){ 68 | int iBool = jx9_value_to_bool(pIn); 69 | sxu32 iLen; 70 | /* true/false */ 71 | iLen = iBool ? sizeof("true") : sizeof("false"); 72 | SyBlobAppend(pOut, iBool ? "true" : "false", iLen-1); 73 | }else if( jx9_value_is_numeric(pIn) && !jx9_value_is_string(pIn) ){ 74 | const char *zNum; 75 | /* Get a string representation of the number */ 76 | zNum = jx9_value_to_string(pIn, &nByte); 77 | SyBlobAppend(pOut,zNum,nByte); 78 | }else if( jx9_value_is_string(pIn) ){ 79 | const char *zIn, *zEnd; 80 | int c; 81 | /* Encode the string */ 82 | zIn = jx9_value_to_string(pIn, &nByte); 83 | zEnd = &zIn[nByte]; 84 | /* Append the double quote */ 85 | SyBlobAppend(pOut,"\"", sizeof(char)); 86 | for(;;){ 87 | if( zIn >= zEnd ){ 88 | /* No more input to process */ 89 | break; 90 | } 91 | c = zIn[0]; 92 | /* Advance the stream cursor */ 93 | zIn++; 94 | if( c == '"' || c == '\\' ){ 95 | /* Unescape the character */ 96 | SyBlobAppend(pOut,"\\", sizeof(char)); 97 | } 98 | /* Append character verbatim */ 99 | SyBlobAppend(pOut,(const char *)&c,sizeof(char)); 100 | } 101 | /* Append the double quote */ 102 | SyBlobAppend(pOut,"\"",sizeof(char)); 103 | }else if( jx9_value_is_json_array(pIn) ){ 104 | /* Encode the array/object */ 105 | pData->isFirst = 1; 106 | if( jx9_value_is_json_object(pIn) ){ 107 | /* Encode the object instance */ 108 | pData->isFirst = 1; 109 | /* Append the curly braces */ 110 | SyBlobAppend(pOut, "{",sizeof(char)); 111 | /* Iterate throw object attribute */ 112 | jx9_array_walk(pIn,VmJsonObjectEncode, pData); 113 | /* Append the closing curly braces */ 114 | SyBlobAppend(pOut, "}",sizeof(char)); 115 | }else{ 116 | /* Append the square bracket or curly braces */ 117 | SyBlobAppend(pOut,"[",sizeof(char)); 118 | /* Iterate throw array entries */ 119 | jx9_array_walk(pIn, VmJsonArrayEncode, pData); 120 | /* Append the closing square bracket or curly braces */ 121 | SyBlobAppend(pOut,"]",sizeof(char)); 122 | } 123 | }else{ 124 | /* Can't happen */ 125 | SyBlobAppend(pOut,"null",sizeof("null")-1); 126 | } 127 | /* All done */ 128 | return JX9_OK; 129 | } 130 | /* 131 | * The following walker callback is invoked each time we need 132 | * to encode an array to JSON. 133 | */ 134 | static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData) 135 | { 136 | json_private_data *pJson = (json_private_data *)pUserData; 137 | if( pJson->nRecCount > 31 ){ 138 | /* Recursion limit reached, return immediately */ 139 | SXUNUSED(pKey); /* cc warning */ 140 | return JX9_OK; 141 | } 142 | if( !pJson->isFirst ){ 143 | /* Append the colon first */ 144 | SyBlobAppend(pJson->pOut,",",(int)sizeof(char)); 145 | } 146 | /* Encode the value */ 147 | pJson->nRecCount++; 148 | VmJsonEncode(pValue, pJson); 149 | pJson->nRecCount--; 150 | pJson->isFirst = 0; 151 | return JX9_OK; 152 | } 153 | /* 154 | * The following walker callback is invoked each time we need to encode 155 | * a object instance [i.e: Object in the JX9 jargon] to JSON. 156 | */ 157 | static int VmJsonObjectEncode(jx9_value *pKey,jx9_value *pValue,void *pUserData) 158 | { 159 | json_private_data *pJson = (json_private_data *)pUserData; 160 | const char *zKey; 161 | int nByte; 162 | if( pJson->nRecCount > 31 ){ 163 | /* Recursion limit reached, return immediately */ 164 | return JX9_OK; 165 | } 166 | if( !pJson->isFirst ){ 167 | /* Append the colon first */ 168 | SyBlobAppend(pJson->pOut,",",sizeof(char)); 169 | } 170 | /* Extract a string representation of the key */ 171 | zKey = jx9_value_to_string(pKey, &nByte); 172 | /* Append the key and the double colon */ 173 | if( nByte > 0 ){ 174 | SyBlobAppend(pJson->pOut,"\"",sizeof(char)); 175 | SyBlobAppend(pJson->pOut,zKey,(sxu32)nByte); 176 | SyBlobAppend(pJson->pOut,"\"",sizeof(char)); 177 | }else{ 178 | /* Can't happen */ 179 | SyBlobAppend(pJson->pOut,"null",sizeof("null")-1); 180 | } 181 | SyBlobAppend(pJson->pOut,":",sizeof(char)); 182 | /* Encode the value */ 183 | pJson->nRecCount++; 184 | VmJsonEncode(pValue, pJson); 185 | pJson->nRecCount--; 186 | pJson->isFirst = 0; 187 | return JX9_OK; 188 | } 189 | /* 190 | * Returns a string containing the JSON representation of value. 191 | * In other words, perform the serialization of the given JSON object. 192 | */ 193 | JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut) 194 | { 195 | json_private_data sJson; 196 | /* Prepare the JSON data */ 197 | sJson.nRecCount = 0; 198 | sJson.pOut = pOut; 199 | sJson.isFirst = 1; 200 | sJson.iFlags = 0; 201 | /* Perform the encoding operation */ 202 | VmJsonEncode(pValue, &sJson); 203 | /* All done */ 204 | return JX9_OK; 205 | } 206 | /* Possible tokens from the JSON tokenization process */ 207 | #define JSON_TK_TRUE 0x001 /* Boolean true */ 208 | #define JSON_TK_FALSE 0x002 /* Boolean false */ 209 | #define JSON_TK_STR 0x004 /* String enclosed in double quotes */ 210 | #define JSON_TK_NULL 0x008 /* null */ 211 | #define JSON_TK_NUM 0x010 /* Numeric */ 212 | #define JSON_TK_OCB 0x020 /* Open curly braces '{' */ 213 | #define JSON_TK_CCB 0x040 /* Closing curly braces '}' */ 214 | #define JSON_TK_OSB 0x080 /* Open square bracke '[' */ 215 | #define JSON_TK_CSB 0x100 /* Closing square bracket ']' */ 216 | #define JSON_TK_COLON 0x200 /* Single colon ':' */ 217 | #define JSON_TK_COMMA 0x400 /* Single comma ',' */ 218 | #define JSON_TK_ID 0x800 /* ID */ 219 | #define JSON_TK_INVALID 0x1000 /* Unexpected token */ 220 | /* 221 | * Tokenize an entire JSON input. 222 | * Get a single low-level token from the input file. 223 | * Update the stream pointer so that it points to the first 224 | * character beyond the extracted token. 225 | */ 226 | static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData) 227 | { 228 | int *pJsonErr = (int *)pUserData; 229 | SyString *pStr; 230 | int c; 231 | /* Ignore leading white spaces */ 232 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ 233 | /* Advance the stream cursor */ 234 | if( pStream->zText[0] == '\n' ){ 235 | /* Update line counter */ 236 | pStream->nLine++; 237 | } 238 | pStream->zText++; 239 | } 240 | if( pStream->zText >= pStream->zEnd ){ 241 | /* End of input reached */ 242 | SXUNUSED(pCtxData); /* cc warning */ 243 | return SXERR_EOF; 244 | } 245 | /* Record token starting position and line */ 246 | pToken->nLine = pStream->nLine; 247 | pToken->pUserData = 0; 248 | pStr = &pToken->sData; 249 | SyStringInitFromBuf(pStr, pStream->zText, 0); 250 | if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ 251 | /* The following code fragment is taken verbatim from the xPP source tree. 252 | * xPP is a modern embeddable macro processor with advanced features useful for 253 | * application seeking for a production quality, ready to use macro processor. 254 | * xPP is a widely used library developed and maintened by Symisc Systems. 255 | * You can reach the xPP home page by following this link: 256 | * http://xpp.symisc.net/ 257 | */ 258 | const unsigned char *zIn; 259 | /* Isolate UTF-8 or alphanumeric stream */ 260 | if( pStream->zText[0] < 0xc0 ){ 261 | pStream->zText++; 262 | } 263 | for(;;){ 264 | zIn = pStream->zText; 265 | if( zIn[0] >= 0xc0 ){ 266 | zIn++; 267 | /* UTF-8 stream */ 268 | while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ 269 | zIn++; 270 | } 271 | } 272 | /* Skip alphanumeric stream */ 273 | while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ 274 | zIn++; 275 | } 276 | if( zIn == pStream->zText ){ 277 | /* Not an UTF-8 or alphanumeric stream */ 278 | break; 279 | } 280 | /* Synchronize pointers */ 281 | pStream->zText = zIn; 282 | } 283 | /* Record token length */ 284 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); 285 | /* A simple identifier */ 286 | pToken->nType = JSON_TK_ID; 287 | if( pStr->nByte == sizeof("true") -1 && SyStrnicmp(pStr->zString, "true", sizeof("true")-1) == 0 ){ 288 | /* boolean true */ 289 | pToken->nType = JSON_TK_TRUE; 290 | }else if( pStr->nByte == sizeof("false") -1 && SyStrnicmp(pStr->zString,"false", sizeof("false")-1) == 0 ){ 291 | /* boolean false */ 292 | pToken->nType = JSON_TK_FALSE; 293 | }else if( pStr->nByte == sizeof("null") -1 && SyStrnicmp(pStr->zString,"null", sizeof("null")-1) == 0 ){ 294 | /* NULL */ 295 | pToken->nType = JSON_TK_NULL; 296 | } 297 | return SXRET_OK; 298 | } 299 | if( pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']' 300 | || pStream->zText[0] == ':' || pStream->zText[0] == ',' ){ 301 | /* Single character */ 302 | c = pStream->zText[0]; 303 | /* Set token type */ 304 | switch(c){ 305 | case '[': pToken->nType = JSON_TK_OSB; break; 306 | case '{': pToken->nType = JSON_TK_OCB; break; 307 | case '}': pToken->nType = JSON_TK_CCB; break; 308 | case ']': pToken->nType = JSON_TK_CSB; break; 309 | case ':': pToken->nType = JSON_TK_COLON; break; 310 | case ',': pToken->nType = JSON_TK_COMMA; break; 311 | default: 312 | break; 313 | } 314 | /* Advance the stream cursor */ 315 | pStream->zText++; 316 | }else if( pStream->zText[0] == '"') { 317 | /* JSON string */ 318 | pStream->zText++; 319 | pStr->zString++; 320 | /* Delimit the string */ 321 | while( pStream->zText < pStream->zEnd ){ 322 | if( pStream->zText[0] == '"' && pStream->zText[-1] != '\\' ){ 323 | break; 324 | } 325 | if( pStream->zText[0] == '\n' ){ 326 | /* Update line counter */ 327 | pStream->nLine++; 328 | } 329 | pStream->zText++; 330 | } 331 | if( pStream->zText >= pStream->zEnd ){ 332 | /* Missing closing '"' */ 333 | pToken->nType = JSON_TK_INVALID; 334 | *pJsonErr = SXERR_SYNTAX; 335 | }else{ 336 | pToken->nType = JSON_TK_STR; 337 | pStream->zText++; /* Jump the closing double quotes */ 338 | } 339 | }else if( (pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) 340 | || pStream->zText[0] == '-' || pStream->zText[0] == '+' ){ 341 | /* Number */ 342 | pStream->zText++; 343 | pToken->nType = JSON_TK_NUM; 344 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ 345 | pStream->zText++; 346 | } 347 | if( pStream->zText < pStream->zEnd ){ 348 | c = pStream->zText[0]; 349 | if( c == '.' ){ 350 | /* Real number */ 351 | pStream->zText++; 352 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ 353 | pStream->zText++; 354 | } 355 | if( pStream->zText < pStream->zEnd ){ 356 | c = pStream->zText[0]; 357 | if( c=='e' || c=='E' ){ 358 | pStream->zText++; 359 | if( pStream->zText < pStream->zEnd ){ 360 | c = pStream->zText[0]; 361 | if( c =='+' || c=='-' ){ 362 | pStream->zText++; 363 | } 364 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ 365 | pStream->zText++; 366 | } 367 | } 368 | } 369 | } 370 | }else if( c=='e' || c=='E' ){ 371 | /* Real number */ 372 | pStream->zText++; 373 | if( pStream->zText < pStream->zEnd ){ 374 | c = pStream->zText[0]; 375 | if( c =='+' || c=='-' ){ 376 | pStream->zText++; 377 | } 378 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ 379 | pStream->zText++; 380 | } 381 | } 382 | } 383 | } 384 | }else{ 385 | /* Unexpected token */ 386 | pToken->nType = JSON_TK_INVALID; 387 | /* Advance the stream cursor */ 388 | pStream->zText++; 389 | *pJsonErr = SXERR_SYNTAX; 390 | /* Abort processing immediatley */ 391 | return SXERR_ABORT; 392 | } 393 | /* record token length */ 394 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); 395 | if( pToken->nType == JSON_TK_STR ){ 396 | pStr->nByte--; 397 | } 398 | /* Return to the lexer */ 399 | return SXRET_OK; 400 | } 401 | /* 402 | * JSON decoded input consumer callback signature. 403 | */ 404 | typedef int (*ProcJSONConsumer)(jx9_context *, jx9_value *, jx9_value *, void *); 405 | /* 406 | * JSON decoder state is kept in the following structure. 407 | */ 408 | typedef struct json_decoder json_decoder; 409 | struct json_decoder 410 | { 411 | jx9_context *pCtx; /* Call context */ 412 | ProcJSONConsumer xConsumer; /* Consumer callback */ 413 | void *pUserData; /* Last argument to xConsumer() */ 414 | int iFlags; /* Configuration flags */ 415 | SyToken *pIn; /* Token stream */ 416 | SyToken *pEnd; /* End of the token stream */ 417 | int rec_count; /* Current nesting level */ 418 | int *pErr; /* JSON decoding error if any */ 419 | }; 420 | /* Forward declaration */ 421 | static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData); 422 | /* 423 | * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store 424 | * the result in the given jx9_value. 425 | */ 426 | static void VmJsonDequoteString(const SyString *pStr, jx9_value *pWorker) 427 | { 428 | const char *zIn = pStr->zString; 429 | const char *zEnd = &pStr->zString[pStr->nByte]; 430 | const char *zCur; 431 | int c; 432 | /* Mark the value as a string */ 433 | jx9_value_string(pWorker, "", 0); /* Empty string */ 434 | for(;;){ 435 | zCur = zIn; 436 | while( zIn < zEnd && zIn[0] != '\\' ){ 437 | zIn++; 438 | } 439 | if( zIn > zCur ){ 440 | /* Append chunk verbatim */ 441 | jx9_value_string(pWorker, zCur, (int)(zIn-zCur)); 442 | } 443 | zIn++; 444 | if( zIn >= zEnd ){ 445 | /* End of the input reached */ 446 | break; 447 | } 448 | c = zIn[0]; 449 | /* Unescape the character */ 450 | switch(c){ 451 | case '"': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; 452 | case '\\': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; 453 | case 'n': jx9_value_string(pWorker, "\n", (int)sizeof(char)); break; 454 | case 'r': jx9_value_string(pWorker, "\r", (int)sizeof(char)); break; 455 | case 't': jx9_value_string(pWorker, "\t", (int)sizeof(char)); break; 456 | case 'f': jx9_value_string(pWorker, "\f", (int)sizeof(char)); break; 457 | default: 458 | jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); 459 | break; 460 | } 461 | /* Advance the stream cursor */ 462 | zIn++; 463 | } 464 | } 465 | /* 466 | * Returns a jx9_value holding the image of a JSON string. In other word perform a JSON decoding operation. 467 | * According to wikipedia 468 | * JSON's basic types are: 469 | * Number (double precision floating-point format in JavaScript, generally depends on implementation) 470 | * String (double-quoted Unicode, with backslash escaping) 471 | * Boolean (true or false) 472 | * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values 473 | * do not need to be of the same type) 474 | * Object (an unordered collection of key:value pairs with the ':' character separating the key 475 | * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should 476 | * be distinct from each other) 477 | * null (empty) 478 | * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ", "). 479 | */ 480 | static sxi32 VmJsonDecode( 481 | json_decoder *pDecoder, /* JSON decoder */ 482 | jx9_value *pArrayKey /* Key for the decoded array */ 483 | ){ 484 | jx9_value *pWorker; /* Worker variable */ 485 | sxi32 rc; 486 | /* Check if we do not nest to much */ 487 | if( pDecoder->rec_count > 31 ){ 488 | /* Nesting limit reached, abort decoding immediately */ 489 | return SXERR_ABORT; 490 | } 491 | if( pDecoder->pIn->nType & (JSON_TK_STR|JSON_TK_ID|JSON_TK_TRUE|JSON_TK_FALSE|JSON_TK_NULL|JSON_TK_NUM) ){ 492 | /* Scalar value */ 493 | pWorker = jx9_context_new_scalar(pDecoder->pCtx); 494 | if( pWorker == 0 ){ 495 | jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); 496 | /* Abort the decoding operation immediately */ 497 | return SXERR_ABORT; 498 | } 499 | /* Reflect the JSON image */ 500 | if( pDecoder->pIn->nType & JSON_TK_NULL ){ 501 | /* Nullify the value.*/ 502 | jx9_value_null(pWorker); 503 | }else if( pDecoder->pIn->nType & (JSON_TK_TRUE|JSON_TK_FALSE) ){ 504 | /* Boolean value */ 505 | jx9_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0 ); 506 | }else if( pDecoder->pIn->nType & JSON_TK_NUM ){ 507 | SyString *pStr = &pDecoder->pIn->sData; 508 | /* 509 | * Numeric value. 510 | * Get a string representation first then try to get a numeric 511 | * value. 512 | */ 513 | jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); 514 | /* Obtain a numeric representation */ 515 | jx9MemObjToNumeric(pWorker); 516 | }else if( pDecoder->pIn->nType & JSON_TK_ID ){ 517 | SyString *pStr = &pDecoder->pIn->sData; 518 | jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); 519 | }else{ 520 | /* Dequote the string */ 521 | VmJsonDequoteString(&pDecoder->pIn->sData, pWorker); 522 | } 523 | /* Invoke the consumer callback */ 524 | rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData); 525 | if( rc == SXERR_ABORT ){ 526 | return SXERR_ABORT; 527 | } 528 | /* All done, advance the stream cursor */ 529 | pDecoder->pIn++; 530 | }else if( pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) { 531 | ProcJSONConsumer xOld; 532 | void *pOld; 533 | /* Array representation*/ 534 | pDecoder->pIn++; 535 | /* Create a working array */ 536 | pWorker = jx9_context_new_array(pDecoder->pCtx); 537 | if( pWorker == 0 ){ 538 | jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); 539 | /* Abort the decoding operation immediately */ 540 | return SXERR_ABORT; 541 | } 542 | /* Save the old consumer */ 543 | xOld = pDecoder->xConsumer; 544 | pOld = pDecoder->pUserData; 545 | /* Set the new consumer */ 546 | pDecoder->xConsumer = VmJsonArrayDecoder; 547 | pDecoder->pUserData = pWorker; 548 | /* Decode the array */ 549 | for(;;){ 550 | /* Jump trailing comma. Note that the standard JX9 engine will not let you 551 | * do this. 552 | */ 553 | while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ 554 | pDecoder->pIn++; 555 | } 556 | if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/ ){ 557 | if( pDecoder->pIn < pDecoder->pEnd ){ 558 | pDecoder->pIn++; /* Jump the trailing ']' */ 559 | } 560 | break; 561 | } 562 | /* Recurse and decode the entry */ 563 | pDecoder->rec_count++; 564 | rc = VmJsonDecode(pDecoder, 0); 565 | pDecoder->rec_count--; 566 | if( rc == SXERR_ABORT ){ 567 | /* Abort processing immediately */ 568 | return SXERR_ABORT; 569 | } 570 | /*The cursor is automatically advanced by the VmJsonDecode() function */ 571 | if( (pDecoder->pIn < pDecoder->pEnd) && 572 | ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/|JSON_TK_COMMA/*','*/))==0) ){ 573 | /* Unexpected token, abort immediatley */ 574 | *pDecoder->pErr = SXERR_SYNTAX; 575 | return SXERR_ABORT; 576 | } 577 | } 578 | /* Restore the old consumer */ 579 | pDecoder->xConsumer = xOld; 580 | pDecoder->pUserData = pOld; 581 | /* Invoke the old consumer on the decoded array */ 582 | xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); 583 | }else if( pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) { 584 | ProcJSONConsumer xOld; 585 | jx9_value *pKey; 586 | void *pOld; 587 | /* Object representation*/ 588 | pDecoder->pIn++; 589 | /* Create a working array */ 590 | pWorker = jx9_context_new_array(pDecoder->pCtx); 591 | pKey = jx9_context_new_scalar(pDecoder->pCtx); 592 | if( pWorker == 0 || pKey == 0){ 593 | jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); 594 | /* Abort the decoding operation immediately */ 595 | return SXERR_ABORT; 596 | } 597 | /* Save the old consumer */ 598 | xOld = pDecoder->xConsumer; 599 | pOld = pDecoder->pUserData; 600 | /* Set the new consumer */ 601 | pDecoder->xConsumer = VmJsonArrayDecoder; 602 | pDecoder->pUserData = pWorker; 603 | /* Decode the object */ 604 | for(;;){ 605 | /* Jump trailing comma. Note that the standard JX9 engine will not let you 606 | * do this. 607 | */ 608 | while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ 609 | pDecoder->pIn++; 610 | } 611 | if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/ ){ 612 | if( pDecoder->pIn < pDecoder->pEnd ){ 613 | pDecoder->pIn++; /* Jump the trailing ']' */ 614 | } 615 | break; 616 | } 617 | if( (pDecoder->pIn->nType & (JSON_TK_ID|JSON_TK_STR)) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd 618 | || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0){ 619 | /* Syntax error, return immediately */ 620 | *pDecoder->pErr = SXERR_SYNTAX; 621 | return SXERR_ABORT; 622 | } 623 | if( pDecoder->pIn->nType & JSON_TK_ID ){ 624 | SyString *pStr = &pDecoder->pIn->sData; 625 | jx9_value_string(pKey, pStr->zString, (int)pStr->nByte); 626 | }else{ 627 | /* Dequote the key */ 628 | VmJsonDequoteString(&pDecoder->pIn->sData, pKey); 629 | } 630 | /* Jump the key and the colon */ 631 | pDecoder->pIn += 2; 632 | /* Recurse and decode the value */ 633 | pDecoder->rec_count++; 634 | rc = VmJsonDecode(pDecoder, pKey); 635 | pDecoder->rec_count--; 636 | if( rc == SXERR_ABORT ){ 637 | /* Abort processing immediately */ 638 | return SXERR_ABORT; 639 | } 640 | /* Reset the internal buffer of the key */ 641 | jx9_value_reset_string_cursor(pKey); 642 | /*The cursor is automatically advanced by the VmJsonDecode() function */ 643 | } 644 | /* Restore the old consumer */ 645 | pDecoder->xConsumer = xOld; 646 | pDecoder->pUserData = pOld; 647 | /* Invoke the old consumer on the decoded object*/ 648 | xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); 649 | /* Release the key */ 650 | jx9_context_release_value(pDecoder->pCtx, pKey); 651 | }else{ 652 | /* Unexpected token */ 653 | return SXERR_ABORT; /* Abort immediately */ 654 | } 655 | /* Release the worker variable */ 656 | jx9_context_release_value(pDecoder->pCtx, pWorker); 657 | return SXRET_OK; 658 | } 659 | /* 660 | * The following JSON decoder callback is invoked each time 661 | * a JSON array representation [i.e: [15, "hello", FALSE] ] 662 | * is being decoded. 663 | */ 664 | static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) 665 | { 666 | jx9_value *pArray = (jx9_value *)pUserData; 667 | /* Insert the entry */ 668 | jx9_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */ 669 | SXUNUSED(pCtx); /* cc warning */ 670 | /* All done */ 671 | return SXRET_OK; 672 | } 673 | /* 674 | * Standard JSON decoder callback. 675 | */ 676 | static int VmJsonDefaultDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) 677 | { 678 | /* Return the value directly */ 679 | jx9_result_value(pCtx, pWorker); /* Will make it's own copy */ 680 | SXUNUSED(pKey); /* cc warning */ 681 | SXUNUSED(pUserData); 682 | /* All done */ 683 | return SXRET_OK; 684 | } 685 | /* 686 | * Exported JSON decoding interface 687 | */ 688 | JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte) 689 | { 690 | jx9_vm *pVm = pCtx->pVm; 691 | json_decoder sDecoder; 692 | SySet sToken; 693 | SyLex sLex; 694 | sxi32 rc; 695 | /* Tokenize the input */ 696 | SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken)); 697 | rc = SXRET_OK; 698 | SyLexInit(&sLex, &sToken, VmJsonTokenize, &rc); 699 | SyLexTokenizeInput(&sLex,zJSON,(sxu32)nByte, 0, 0, 0); 700 | if( rc != SXRET_OK ){ 701 | /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */ 702 | SyLexRelease(&sLex); 703 | SySetRelease(&sToken); 704 | /* return NULL */ 705 | jx9_result_null(pCtx); 706 | return JX9_OK; 707 | } 708 | /* Fill the decoder */ 709 | sDecoder.pCtx = pCtx; 710 | sDecoder.pErr = &rc; 711 | sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken); 712 | sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)]; 713 | sDecoder.iFlags = 0; 714 | sDecoder.rec_count = 0; 715 | /* Set a default consumer */ 716 | sDecoder.xConsumer = VmJsonDefaultDecoder; 717 | sDecoder.pUserData = 0; 718 | /* Decode the raw JSON input */ 719 | rc = VmJsonDecode(&sDecoder, 0); 720 | if( rc == SXERR_ABORT ){ 721 | /* 722 | * Something goes wrong while decoding JSON input.Return NULL. 723 | */ 724 | jx9_result_null(pCtx); 725 | } 726 | /* Clean-up the mess left behind */ 727 | SyLexRelease(&sLex); 728 | SySetRelease(&sToken); 729 | /* All done */ 730 | return JX9_OK; 731 | } 732 | --------------------------------------------------------------------------------