├── .clusterfuzzlite ├── Dockerfile ├── build.sh ├── fuzzer.c └── project.yaml ├── .github └── workflows │ └── cflite_pr.yml ├── .gitignore ├── CreateTableParser.xcodeproj └── project.pbxproj ├── LICENSE ├── README.md ├── debug ├── main.c ├── sql3parse_debug.c └── sql3parse_debug.h ├── sql3parse_table.c └── sql3parse_table.h /.clusterfuzzlite/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/oss-fuzz-base/base-builder 2 | RUN apt-get update && apt-get install -y make autoconf automake libtool 3 | COPY . $SRC/sqlite-createtable-parser 4 | COPY .clusterfuzzlite/build.sh $SRC/build.sh 5 | COPY .clusterfuzzlite/*.c $SRC/ 6 | WORKDIR sqlite-createtable-parser 7 | -------------------------------------------------------------------------------- /.clusterfuzzlite/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | $CC $CFLAGS -c sql3parse_table.c 3 | llvm-ar rcs libfuzz.a *.o 4 | 5 | 6 | $CC $CFLAGS $LIB_FUZZING_ENGINE $SRC/fuzzer.c -Wl,--whole-archive $SRC/sqlite-createtable-parser/libfuzz.a -Wl,--allow-multiple-definition -I$SRC/sqlite-createtable-parser/ -I$SRC/sqlite-createtable-parser/debug -o $OUT/fuzzer 7 | -------------------------------------------------------------------------------- /.clusterfuzzlite/fuzzer.c: -------------------------------------------------------------------------------- 1 | // Heuristic: FuzzerGenHeuristic6 :: Target: sql3parse_table 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sql3parse_debug.h" 8 | #include "sql3parse_table.h" 9 | 10 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 11 | if (size == 0) { 12 | return 0; 13 | } 14 | 15 | // Null-terminate the input data 16 | char *input = (char *)malloc(size + 1); 17 | if (!input) { 18 | return 0; 19 | } 20 | memcpy(input, data, size); 21 | input[size] = '\0'; 22 | 23 | sql3error_code error; 24 | sql3table *result = sql3parse_table((const char *)input, size, &error); 25 | 26 | free(input); 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /.clusterfuzzlite/project.yaml: -------------------------------------------------------------------------------- 1 | language: c 2 | -------------------------------------------------------------------------------- /.github/workflows/cflite_pr.yml: -------------------------------------------------------------------------------- 1 | name: ClusterFuzzLite PR fuzzing 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: [ master ] 6 | permissions: read-all 7 | jobs: 8 | PR: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | sanitizer: [address] 14 | steps: 15 | - name: Build Fuzzers (${{ matrix.sanitizer }}) 16 | id: build 17 | uses: google/clusterfuzzlite/actions/build_fuzzers@v1 18 | with: 19 | sanitizer: ${{ matrix.sanitizer }} 20 | language: c++ 21 | bad-build-check: false 22 | - name: Run Fuzzers (${{ matrix.sanitizer }}) 23 | id: run 24 | uses: google/clusterfuzzlite/actions/run_fuzzers@v1 25 | with: 26 | github-token: ${{ secrets.GITHUB_TOKEN }} 27 | fuzz-seconds: 100 28 | mode: 'code-change' 29 | report-unreproducible-crashes: false 30 | sanitizer: ${{ matrix.sanitizer }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # XCode user-specific stuff. 2 | xcuserdata/ 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | # TODO: Comment the next line if you want to checkin your web deploy settings 143 | # but database connection strings (with potential passwords) will be unencrypted 144 | *.pubxml 145 | *.publishproj 146 | 147 | # NuGet Packages 148 | *.nupkg 149 | # The packages folder can be ignored because of Package Restore 150 | **/packages/* 151 | # except build/, which is used as an MSBuild target. 152 | !**/packages/build/ 153 | # Uncomment if necessary however generally it will be regenerated when needed 154 | #!**/packages/repositories.config 155 | # NuGet v3's project.json files produces more ignoreable files 156 | *.nuget.props 157 | *.nuget.targets 158 | 159 | # Microsoft Azure Build Output 160 | csx/ 161 | *.build.csdef 162 | 163 | # Microsoft Azure Emulator 164 | ecf/ 165 | rcf/ 166 | 167 | # Microsoft Azure ApplicationInsights config file 168 | ApplicationInsights.config 169 | 170 | # Windows Store app package directory 171 | AppPackages/ 172 | BundleArtifacts/ 173 | 174 | # Visual Studio cache files 175 | # files ending in .cache can be ignored 176 | *.[Cc]ache 177 | # but keep track of directories ending in .cache 178 | !*.[Cc]ache/ 179 | 180 | # Others 181 | ClientBin/ 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # Paket dependency manager 235 | .paket/paket.exe 236 | 237 | # FAKE - F# Make 238 | .fake/ 239 | 240 | *.xcworkspacedata 241 | CreateTableParser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist 242 | .DS_Store 243 | -------------------------------------------------------------------------------- /CreateTableParser.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A90DE075211B942F00E14077 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = A90DE073211B942F00E14077 /* main.c */; }; 11 | A90DE076211B942F00E14077 /* sql3parse_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = A90DE074211B942F00E14077 /* sql3parse_debug.c */; }; 12 | A9B040A11C870E750001459D /* sql3parse_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A9B0409F1C870E750001459D /* sql3parse_table.c */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | A9B67B9C1C70691E00B772A9 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = /usr/share/man/man1/; 20 | dstSubfolderSpec = 0; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 1; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | A90DE072211B942F00E14077 /* sql3parse_debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sql3parse_debug.h; path = debug/sql3parse_debug.h; sourceTree = ""; }; 29 | A90DE073211B942F00E14077 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = debug/main.c; sourceTree = ""; }; 30 | A90DE074211B942F00E14077 /* sql3parse_debug.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sql3parse_debug.c; path = debug/sql3parse_debug.c; sourceTree = ""; }; 31 | A9B0409F1C870E750001459D /* sql3parse_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sql3parse_table.c; sourceTree = SOURCE_ROOT; }; 32 | A9B040A01C870E750001459D /* sql3parse_table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sql3parse_table.h; sourceTree = SOURCE_ROOT; }; 33 | A9B67B9E1C70691E00B772A9 /* CreateTableParser */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = CreateTableParser; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | A9B67B9B1C70691E00B772A9 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | A9B67B951C70691E00B772A9 = { 48 | isa = PBXGroup; 49 | children = ( 50 | A90DE073211B942F00E14077 /* main.c */, 51 | A9B0409F1C870E750001459D /* sql3parse_table.c */, 52 | A9B040A01C870E750001459D /* sql3parse_table.h */, 53 | A90DE074211B942F00E14077 /* sql3parse_debug.c */, 54 | A90DE072211B942F00E14077 /* sql3parse_debug.h */, 55 | A9B67B9F1C70691E00B772A9 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | A9B67B9F1C70691E00B772A9 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | A9B67B9E1C70691E00B772A9 /* CreateTableParser */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | A9B67B9D1C70691E00B772A9 /* CreateTableParser */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = A9B67BA51C70691E00B772A9 /* Build configuration list for PBXNativeTarget "CreateTableParser" */; 73 | buildPhases = ( 74 | A9B67B9A1C70691E00B772A9 /* Sources */, 75 | A9B67B9B1C70691E00B772A9 /* Frameworks */, 76 | A9B67B9C1C70691E00B772A9 /* CopyFiles */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = CreateTableParser; 83 | productName = CreateTableParser; 84 | productReference = A9B67B9E1C70691E00B772A9 /* CreateTableParser */; 85 | productType = "com.apple.product-type.tool"; 86 | }; 87 | /* End PBXNativeTarget section */ 88 | 89 | /* Begin PBXProject section */ 90 | A9B67B961C70691E00B772A9 /* Project object */ = { 91 | isa = PBXProject; 92 | attributes = { 93 | LastUpgradeCheck = 1420; 94 | ORGANIZATIONNAME = Creolabs; 95 | TargetAttributes = { 96 | A9B67B9D1C70691E00B772A9 = { 97 | CreatedOnToolsVersion = 7.2.1; 98 | }; 99 | }; 100 | }; 101 | buildConfigurationList = A9B67B991C70691E00B772A9 /* Build configuration list for PBXProject "CreateTableParser" */; 102 | compatibilityVersion = "Xcode 3.2"; 103 | developmentRegion = en; 104 | hasScannedForEncodings = 0; 105 | knownRegions = ( 106 | en, 107 | Base, 108 | ); 109 | mainGroup = A9B67B951C70691E00B772A9; 110 | productRefGroup = A9B67B9F1C70691E00B772A9 /* Products */; 111 | projectDirPath = ""; 112 | projectRoot = ""; 113 | targets = ( 114 | A9B67B9D1C70691E00B772A9 /* CreateTableParser */, 115 | ); 116 | }; 117 | /* End PBXProject section */ 118 | 119 | /* Begin PBXSourcesBuildPhase section */ 120 | A9B67B9A1C70691E00B772A9 /* Sources */ = { 121 | isa = PBXSourcesBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | A90DE076211B942F00E14077 /* sql3parse_debug.c in Sources */, 125 | A90DE075211B942F00E14077 /* main.c in Sources */, 126 | A9B040A11C870E750001459D /* sql3parse_table.c in Sources */, 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXSourcesBuildPhase section */ 131 | 132 | /* Begin XCBuildConfiguration section */ 133 | A9B67BA31C70691E00B772A9 /* Debug */ = { 134 | isa = XCBuildConfiguration; 135 | buildSettings = { 136 | ALWAYS_SEARCH_USER_PATHS = NO; 137 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 138 | CLANG_CXX_LIBRARY = "libc++"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 142 | CLANG_WARN_BOOL_CONVERSION = YES; 143 | CLANG_WARN_COMMA = YES; 144 | CLANG_WARN_CONSTANT_CONVERSION = YES; 145 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 146 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 147 | CLANG_WARN_EMPTY_BODY = YES; 148 | CLANG_WARN_ENUM_CONVERSION = YES; 149 | CLANG_WARN_INFINITE_RECURSION = YES; 150 | CLANG_WARN_INT_CONVERSION = YES; 151 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 152 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 153 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 154 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 155 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 156 | CLANG_WARN_STRICT_PROTOTYPES = YES; 157 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 158 | CLANG_WARN_UNREACHABLE_CODE = YES; 159 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 160 | CODE_SIGN_IDENTITY = "-"; 161 | COPY_PHASE_STRIP = NO; 162 | DEBUG_INFORMATION_FORMAT = dwarf; 163 | ENABLE_STRICT_OBJC_MSGSEND = YES; 164 | ENABLE_TESTABILITY = YES; 165 | GCC_C_LANGUAGE_STANDARD = gnu99; 166 | GCC_DYNAMIC_NO_PIC = NO; 167 | GCC_NO_COMMON_BLOCKS = YES; 168 | GCC_OPTIMIZATION_LEVEL = 0; 169 | GCC_PREPROCESSOR_DEFINITIONS = ( 170 | "DEBUG=1", 171 | "$(inherited)", 172 | ); 173 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 174 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 175 | GCC_WARN_UNDECLARED_SELECTOR = YES; 176 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 177 | GCC_WARN_UNUSED_FUNCTION = YES; 178 | GCC_WARN_UNUSED_VARIABLE = YES; 179 | MACOSX_DEPLOYMENT_TARGET = 10.11; 180 | MTL_ENABLE_DEBUG_INFO = YES; 181 | ONLY_ACTIVE_ARCH = YES; 182 | SDKROOT = macosx; 183 | }; 184 | name = Debug; 185 | }; 186 | A9B67BA41C70691E00B772A9 /* Release */ = { 187 | isa = XCBuildConfiguration; 188 | buildSettings = { 189 | ALWAYS_SEARCH_USER_PATHS = NO; 190 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 191 | CLANG_CXX_LIBRARY = "libc++"; 192 | CLANG_ENABLE_MODULES = YES; 193 | CLANG_ENABLE_OBJC_ARC = YES; 194 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 195 | CLANG_WARN_BOOL_CONVERSION = YES; 196 | CLANG_WARN_COMMA = YES; 197 | CLANG_WARN_CONSTANT_CONVERSION = YES; 198 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 199 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 200 | CLANG_WARN_EMPTY_BODY = YES; 201 | CLANG_WARN_ENUM_CONVERSION = YES; 202 | CLANG_WARN_INFINITE_RECURSION = YES; 203 | CLANG_WARN_INT_CONVERSION = YES; 204 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 205 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 206 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 207 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 208 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 209 | CLANG_WARN_STRICT_PROTOTYPES = YES; 210 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 211 | CLANG_WARN_UNREACHABLE_CODE = YES; 212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 213 | CODE_SIGN_IDENTITY = "-"; 214 | COPY_PHASE_STRIP = NO; 215 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 216 | ENABLE_NS_ASSERTIONS = NO; 217 | ENABLE_STRICT_OBJC_MSGSEND = YES; 218 | GCC_C_LANGUAGE_STANDARD = gnu99; 219 | GCC_NO_COMMON_BLOCKS = YES; 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 222 | GCC_WARN_UNDECLARED_SELECTOR = YES; 223 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 224 | GCC_WARN_UNUSED_FUNCTION = YES; 225 | GCC_WARN_UNUSED_VARIABLE = YES; 226 | MACOSX_DEPLOYMENT_TARGET = 10.11; 227 | MTL_ENABLE_DEBUG_INFO = NO; 228 | SDKROOT = macosx; 229 | }; 230 | name = Release; 231 | }; 232 | A9B67BA61C70691E00B772A9 /* Debug */ = { 233 | isa = XCBuildConfiguration; 234 | buildSettings = { 235 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 236 | PRODUCT_NAME = "$(TARGET_NAME)"; 237 | }; 238 | name = Debug; 239 | }; 240 | A9B67BA71C70691E00B772A9 /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 244 | PRODUCT_NAME = "$(TARGET_NAME)"; 245 | }; 246 | name = Release; 247 | }; 248 | /* End XCBuildConfiguration section */ 249 | 250 | /* Begin XCConfigurationList section */ 251 | A9B67B991C70691E00B772A9 /* Build configuration list for PBXProject "CreateTableParser" */ = { 252 | isa = XCConfigurationList; 253 | buildConfigurations = ( 254 | A9B67BA31C70691E00B772A9 /* Debug */, 255 | A9B67BA41C70691E00B772A9 /* Release */, 256 | ); 257 | defaultConfigurationIsVisible = 0; 258 | defaultConfigurationName = Release; 259 | }; 260 | A9B67BA51C70691E00B772A9 /* Build configuration list for PBXNativeTarget "CreateTableParser" */ = { 261 | isa = XCConfigurationList; 262 | buildConfigurations = ( 263 | A9B67BA61C70691E00B772A9 /* Debug */, 264 | A9B67BA71C70691E00B772A9 /* Release */, 265 | ); 266 | defaultConfigurationIsVisible = 0; 267 | defaultConfigurationName = Release; 268 | }; 269 | /* End XCConfigurationList section */ 270 | }; 271 | rootObject = A9B67B961C70691E00B772A9 /* Project object */; 272 | } 273 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Marco Bambini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SQLite CREATE and ALTER TABLE Parser 2 | A parser for SQLite [create table](https://www.sqlite.org/lang_createtable.html) and [alter table](https://www.sqlite.org/lang_altertable.html) sql statements. 3 | 4 | * Extremely fast parser with no memory copy overhead 5 | * MIT licensed with no dependencies (just drop the C file into your project) 6 | * Never recurses or allocates more memory than it needs 7 | * Very simple API 8 | 9 | ## Motivation 10 | [SQLite](https://www.sqlite.org/) is a very powerful software but it lacks an easy way to extract complete information about tables and columns constraints. This drawback in addition to the lack of full ALTER TABLE support makes alterring a table a very hard task. The built-in sqlite pragmas provide incomplete information and a manual parsing is required in order to extract all the metadata from a table. 11 | ```c 12 | PRAGMA table_info(table-name); 13 | PRAGMA foreign_key_list(table-name); 14 | ``` 15 | CREATE TABLE syntax diagrams can be found on the official [sqlite website](https://www.sqlite.org/lang_createtable.html). 16 | 17 | ## Pre-requisites 18 | - A C99 compiler. 19 | - SQL statement must be successfully compiled by SQLite. 20 | 21 | ## Usage 22 | In order to extract the original CREATE TABLE sql statement you need to query the sqlite_master table from within an SQLite database: 23 | ```sql 24 | SELECT sql FROM sqlite_master WHERE name = 'myTable'; 25 | ``` 26 | 27 | then just include sql3parse_table.h and sql3parse_table.c in your project and invoke: 28 | ```c 29 | // sql is the CREATE TABLE sql statement 30 | // length is the length of sql (if 0 then strlen will be used) 31 | // error is the returned error code (can be NULL) 32 | // return value: NULL in case of error or an opaque pointer in case of success 33 | 34 | sql3table *sql3parse_table (const char *sql, size_t length, sql3error_code *error); 35 | ``` 36 | **sql3table** is an opaque struct that you can introspect using the sql3table* public functions. 37 | 38 | ## API 39 | ```c 40 | // Main Entrypoint 41 | sql3table *sql3parse_table (const char *sql, size_t length, sql3error_code *error); 42 | 43 | // Table information 44 | sql3string *sql3table_schema (sql3table *table); 45 | sql3string *sql3table_name (sql3table *table); 46 | sql3string *sql3table_comment (sql3table *table); 47 | bool sql3table_is_temporary (sql3table *table); 48 | bool sql3table_is_ifnotexists (sql3table *table); 49 | bool sql3table_is_withoutrowid (sql3table *table); 50 | size_t sql3table_num_columns (sql3table *table); 51 | sql3column *sql3table_get_column (sql3table *table, size_t index); 52 | size_t sql3table_num_constraints (sql3table *table); 53 | sql3tableconstraint *sql3table_get_constraint (sql3table *table, size_t index); 54 | void sql3table_free (sql3table *table); 55 | sql3statement_type sql3table_type (sql3table *table); 56 | sql3string *sql3table_current_name (sql3table *table); 57 | sql3string *sql3table_new_name (sql3table *table); 58 | 59 | 60 | // Table constraints 61 | sql3string *sql3table_constraint_name (sql3tableconstraint *tconstraint); 62 | sql3constraint_type sql3table_constraint_type (sql3tableconstraint *tconstraint); 63 | size_t sql3table_constraint_num_idxcolumns (sql3tableconstraint *tconstraint); 64 | sql3idxcolumn *sql3table_constraint_get_idxcolumn (sql3tableconstraint *tconstraint, size_t index); 65 | sql3conflict_clause sql3table_constraint_conflict_clause (sql3tableconstraint *tconstraint); 66 | sql3string *sql3table_constraint_check_expr (sql3tableconstraint *tconstraint); 67 | size_t sql3table_constraint_num_fkcolumns (sql3tableconstraint *tconstraint); 68 | sql3string *sql3table_constraint_get_fkcolumn (sql3tableconstraint *tconstraint, size_t index); 69 | sql3foreignkey *sql3table_constraint_foreignkey_clause (sql3tableconstraint *tconstraint); 70 | 71 | // Column constraints 72 | sql3string *sql3column_name (sql3column *column); 73 | sql3string *sql3column_type (sql3column *column); 74 | sql3string *sql3column_length (sql3column *column); 75 | sql3string *sql3column_constraint_name (sql3column *column); 76 | sql3string *sql3column_comment (sql3column *column); 77 | bool sql3column_is_primarykey (sql3column *column); 78 | bool sql3column_is_autoincrement (sql3column *column); 79 | bool sql3column_is_notnull (sql3column *column); 80 | bool sql3column_is_unique (sql3column *column); 81 | sql3order_clause sql3column_pk_order (sql3column *column); 82 | sql3conflict_clause sql3column_pk_conflictclause (sql3column *column); 83 | sql3conflict_clause sql3column_notnull_conflictclause (sql3column *column); 84 | sql3conflict_clause sql3column_unique_conflictclause (sql3column *column); 85 | sql3string *sql3column_check_expr (sql3column *column); 86 | sql3string *sql3column_default_expr (sql3column *column); 87 | sql3string *sql3column_collate_name (sql3column *column); 88 | sql3foreignkey *sql3column_foreignkey_clause (sql3column *column); 89 | 90 | // Foreign key 91 | sql3string *sql3foreignkey_table (sql3foreignkey *fk); 92 | size_t sql3foreignkey_num_columns (sql3foreignkey *fk); 93 | sql3string *sql3foreignkey_get_column (sql3foreignkey *fk, size_t index); 94 | sql3fk_action sql3foreignkey_ondelete_action (sql3foreignkey *fk); 95 | sql3fk_action sql3foreignkey_onupdate_action (sql3foreignkey *fk); 96 | sql3string *sql3foreignkey_match (sql3foreignkey *fk); 97 | sql3fk_deftype sql3foreignkey_deferrable (sql3foreignkey *fk); 98 | 99 | // Indexed column 100 | sql3string *sql3idxcolumn_name (sql3idxcolumn *idxcolumn); 101 | sql3string *sql3idxcolumn_collate (sql3idxcolumn *idxcolumn); 102 | sql3order_clause sql3idxcolumn_order (sql3idxcolumn *idxcolumn); 103 | 104 | // String Utils 105 | const char *sql3string_ptr (sql3string *s, size_t *length); 106 | const char *sql3string_cstring (sql3string *s); 107 | ``` 108 | 109 | ## Example 110 | Dump to stdout complete table information: 111 | ```c 112 | // all the necessary code is in sql3parse_debug.h/.c 113 | void table_dump (sql3table *table) { 114 | if (!table) return; 115 | 116 | // schema name 117 | sql3string *ptr = sql3table_schema(table); 118 | sql3string_dump(ptr, "Schema Name"); 119 | 120 | // table name 121 | ptr = sql3table_name(table); 122 | sql3string_dump(ptr, "Table Name"); 123 | 124 | // table comment 125 | ptr = sql3table_comment(table); 126 | if (ptr) sql3string_dump(ptr, "Table Comment"); 127 | 128 | // table flags 129 | printf("Temporary: %d\n", sql3table_is_temporary(table)); 130 | printf("If Not Exists: %d\n", sql3table_is_ifnotexists(table)); 131 | printf("Without RowID: %d\n", sql3table_is_withoutrowid(table)); 132 | 133 | // loop to print complete columns info 134 | size_t num_columns = sql3table_num_columns(table); 135 | printf("Num Columns: %zu\n", num_columns); 136 | for (size_t i=0; ioffset >= state->size) 139 | #define PEEK (state->buffer[state->offset]) 140 | #define PEEK2 (state->buffer[state->offset+1]) 141 | #define NEXT (state->buffer[state->offset++]) 142 | #define SKIP_ONE ++state->offset; 143 | #define CHECK_STR(s) if (!s.ptr) return NULL 144 | #define CHECK_IDX(idx1,idx2) if (idx1>=idx2) return NULL 145 | 146 | // MARK: - Public String Functions - 147 | 148 | const char *sql3string_ptr (sql3string *s, size_t *length) { 149 | if (length) *length = s->length; 150 | return s->ptr; 151 | } 152 | 153 | const char *sql3string_cstring (sql3string *s) { 154 | if (!s->ptr) return NULL; 155 | 156 | char *ptr = (char *)SQL3MALLOC0(s->length+1); 157 | if (!ptr) return NULL; 158 | 159 | memcpy(ptr, s->ptr, s->length); 160 | return (const char *)ptr; 161 | } 162 | 163 | 164 | // MARK: - Internal Utils - 165 | 166 | static int str_nocasencmp(const char *s1, const char *s2, size_t n) { 167 | while(n > 0 && tolower((unsigned char)*s1) == tolower((unsigned char)*s2)) { 168 | if(*s1 == '\0') return 0; 169 | s1++; 170 | s2++; 171 | n--; 172 | } 173 | 174 | if(n == 0) return 0; 175 | return tolower((unsigned char)*s1) - tolower((unsigned char)*s2); 176 | } 177 | 178 | static bool symbol_is_space (sql3char c) { 179 | return ((c == ' ') || (c == '\t') || (c == '\v') || (c == '\f')); 180 | } 181 | 182 | static bool symbol_is_newline (sql3char c) { 183 | return ((c == '\n') || (c == '\r')); 184 | } 185 | 186 | static bool symbol_is_toskip (sql3char c) { 187 | // skip whitespaces and newlines 188 | return (symbol_is_space(c) || symbol_is_newline(c)); 189 | } 190 | 191 | static bool symbol_is_comment (sql3char c, sql3state *state) { 192 | if ((c == '-') && (PEEK2 == '-')) return true; 193 | if ((c == '/') && (PEEK2 == '*')) return true; 194 | return false; 195 | } 196 | 197 | static bool symbol_is_alpha (sql3char c) { 198 | if (c == '_') return true; 199 | return isalpha((int)c); 200 | } 201 | 202 | static bool symbol_is_identifier (sql3char c) { 203 | // when called I am already sure first character is alpha so next valid characters are alpha, digit and _ 204 | return ((isalpha(c)) || (isdigit(c)) || (c == '_')); 205 | } 206 | 207 | static bool symbol_is_escape (sql3char c) { 208 | // From Dr. Hipp 209 | // An effort is made to use labels in single-quotes as string literals 210 | // first, as that is the SQL standard. But if if the token does not make 211 | // sense as a string literal, it falls back to being an identifier. This 212 | // is an ugly hack. I originally put in the fall-back logic for 213 | // compatibility with MySQL and I now see that was a mistake. But it is 214 | // used a lot in legacy code, so I cannot take it out. 215 | return ((c == '`') || (c == '\'') || (c == '"') || (c == '[')); 216 | } 217 | 218 | static bool symbol_is_punctuation (sql3char c) { 219 | return ((c == '.') || (c == ',') || (c == '(') || (c == ')') || (c == ';')); 220 | } 221 | 222 | static bool token_is_column_constraint (sql3token_t t) { 223 | return ((t == TOK_CONSTRAINT) || (t == TOK_PRIMARY) || (t == TOK_NOT) || (t == TOK_UNIQUE) || 224 | (t == TOK_CHECK) || (t == TOK_DEFAULT) || (t == TOK_COLLATE) || (t == TOK_REFERENCES)); 225 | } 226 | 227 | static bool token_is_table_constraint (sql3token_t t) { 228 | return ((t == TOK_CONSTRAINT) || (t == TOK_PRIMARY) || (t == TOK_UNIQUE) || 229 | (t == TOK_CHECK) || (t == TOK_FOREIGN)); 230 | } 231 | 232 | // MARK: - Internal Lexer - 233 | 234 | static sql3token_t sql3lexer_keyword (const char *ptr, size_t length) { 235 | 236 | // check if ptr is a reserved keyword 237 | switch (length) { 238 | case 2: 239 | if (str_nocasencmp(ptr, "if", length) == 0) return TOK_IF; 240 | if (str_nocasencmp(ptr, "on", length) == 0) return TOK_ON; 241 | if (str_nocasencmp(ptr, "no", length) == 0) return TOK_NO; 242 | if (str_nocasencmp(ptr, "as", length) == 0) return TOK_AS; 243 | if (str_nocasencmp(ptr, "to", length) == 0) return TOK_TO; 244 | break; 245 | 246 | case 3: 247 | if (str_nocasencmp(ptr, "not", length) == 0) return TOK_NOT; 248 | if (str_nocasencmp(ptr, "key", length) == 0) return TOK_KEY; 249 | if (str_nocasencmp(ptr, "asc", length) == 0) return TOK_ASC; 250 | if (str_nocasencmp(ptr, "set", length) == 0) return TOK_SET; 251 | if (str_nocasencmp(ptr, "add", length) == 0) return TOK_ADD; 252 | break; 253 | 254 | case 4: 255 | if (str_nocasencmp(ptr, "temp", length) == 0) return TOK_TEMP; 256 | if (str_nocasencmp(ptr, "desc", length) == 0) return TOK_DESC; 257 | if (str_nocasencmp(ptr, "null", length) == 0) return TOK_NULL; 258 | if (str_nocasencmp(ptr, "fail", length) == 0) return TOK_FAIL; 259 | if (str_nocasencmp(ptr, "drop", length) == 0) return TOK_DROP; 260 | break; 261 | 262 | case 5: 263 | if (str_nocasencmp(ptr, "table", length) == 0) return TOK_TABLE; 264 | if (str_nocasencmp(ptr, "rowid", length) == 0) return TOK_ROWID; 265 | if (str_nocasencmp(ptr, "check", length) == 0) return TOK_CHECK; 266 | if (str_nocasencmp(ptr, "abort", length) == 0) return TOK_ABORT; 267 | if (str_nocasencmp(ptr, "match", length) == 0) return TOK_MATCH; 268 | if (str_nocasencmp(ptr, "alter", length) == 0) return TOK_ALTER; 269 | break; 270 | 271 | case 6: 272 | if (str_nocasencmp(ptr, "create", length) == 0) return TOK_CREATE; 273 | if (str_nocasencmp(ptr, "exists", length) == 0) return TOK_EXISTS; 274 | if (str_nocasencmp(ptr, "unique", length) == 0) return TOK_UNIQUE; 275 | if (str_nocasencmp(ptr, "ignore", length) == 0) return TOK_IGNORE; 276 | if (str_nocasencmp(ptr, "delete", length) == 0) return TOK_DELETE; 277 | if (str_nocasencmp(ptr, "update", length) == 0) return TOK_UPDATE; 278 | if (str_nocasencmp(ptr, "action", length) == 0) return TOK_ACTION; 279 | if (str_nocasencmp(ptr, "strict", length) == 0) return TOK_STRICT; 280 | if (str_nocasencmp(ptr, "rename", length) == 0) return TOK_RENAME; 281 | if (str_nocasencmp(ptr, "column", length) == 0) return TOK_COLUMN; 282 | break; 283 | 284 | case 7: 285 | if (str_nocasencmp(ptr, "without", length) == 0) return TOK_WITHOUT; 286 | if (str_nocasencmp(ptr, "primary", length) == 0) return TOK_PRIMARY; 287 | if (str_nocasencmp(ptr, "default", length) == 0) return TOK_DEFAULT; 288 | if (str_nocasencmp(ptr, "collate", length) == 0) return TOK_COLLATE; 289 | if (str_nocasencmp(ptr, "replace", length) == 0) return TOK_REPLACE; 290 | if (str_nocasencmp(ptr, "cascade", length) == 0) return TOK_CASCADE; 291 | if (str_nocasencmp(ptr, "foreign", length) == 0) return TOK_FOREIGN; 292 | break; 293 | 294 | case 8: 295 | if (str_nocasencmp(ptr, "conflict", length) == 0) return TOK_CONFLICT; 296 | if (str_nocasencmp(ptr, "rollback", length) == 0) return TOK_ROLLBACK; 297 | if (str_nocasencmp(ptr, "restrict", length) == 0) return TOK_RESTRICT; 298 | if (str_nocasencmp(ptr, "deferred", length) == 0) return TOK_DEFERRED; 299 | break; 300 | 301 | case 9: 302 | if (str_nocasencmp(ptr, "temporary", length) == 0) return TOK_TEMP; 303 | if (str_nocasencmp(ptr, "initially", length) == 0) return TOK_INITIALLY; 304 | if (str_nocasencmp(ptr, "immediate", length) == 0) return TOK_IMMEDIATE; 305 | break; 306 | 307 | case 10: 308 | if (str_nocasencmp(ptr, "constraint", length) == 0) return TOK_CONSTRAINT; 309 | if (str_nocasencmp(ptr, "references", length) == 0) return TOK_REFERENCES; 310 | if (str_nocasencmp(ptr, "deferrable", length) == 0) return TOK_DEFERRABLE; 311 | break; 312 | 313 | case 13: 314 | if (str_nocasencmp(ptr, "autoincrement", length) == 0) return TOK_AUTOINCREMENT; 315 | break; 316 | } 317 | 318 | // no reserved keyword found 319 | return TOK_IDENTIFIER; 320 | } 321 | 322 | sql3token_t sql3lexer_comment (sql3state *state) { 323 | sql3char c1_start = NEXT; 324 | sql3char c2_start = NEXT; 325 | bool is_c_comment = ((c1_start == '/') && (c2_start == '*')); 326 | 327 | size_t offset = state->offset; 328 | const char *ptr = &state->buffer[offset]; 329 | 330 | while (1) { 331 | sql3char c1 = NEXT; 332 | 333 | // EOF case 334 | if (c1 == 0) { 335 | // SQL or C-style comments can be terminated by EOF 336 | break; 337 | } 338 | 339 | // check for end-of-comment condition 340 | if (is_c_comment) { 341 | // c-style comments need two characters to check 342 | sql3char c2 = PEEK; 343 | if ((c1 == '*') && (c2 == '/')) { 344 | (void) NEXT; // consume c2 345 | break; 346 | } 347 | } else { 348 | // -- comments are closed by newline 349 | if (symbol_is_newline(c1)) break; 350 | } 351 | } 352 | 353 | // setup current comment 354 | if (state->comment) { 355 | size_t length = state->offset - offset; 356 | length -= (is_c_comment) ? 2 : 1; // remove */ or newline 357 | state->comment->ptr = ptr; 358 | state->comment->length = length; 359 | //printf("Parsed comment: %.*s\n", (int)length, ptr); 360 | } 361 | 362 | return TOK_COMMENT; 363 | } 364 | 365 | sql3token_t sql3lexer_punctuation (sql3state *state) { 366 | sql3char c = NEXT; 367 | 368 | switch (c) { 369 | case ',': return TOK_COMMA; 370 | case '.': return TOK_DOT; 371 | case '(': return TOK_OPEN_PARENTHESIS; 372 | case ')': return TOK_CLOSED_PARENTHESIS; 373 | case ';': return TOK_SEMICOLON; 374 | } 375 | 376 | return TOK_ERROR; 377 | } 378 | 379 | sql3token_t sql3lexer_alpha (sql3state *state) { 380 | size_t offset = state->offset; 381 | 382 | while (symbol_is_identifier(PEEK)) { 383 | SKIP_ONE; 384 | } 385 | 386 | const char *ptr = &state->buffer[offset]; 387 | size_t length = state->offset - offset; 388 | 389 | sql3token_t t = sql3lexer_keyword(ptr, length); 390 | if (t != TOK_IDENTIFIER) return t; 391 | 392 | // setup internal identifier 393 | state->identifier.ptr = ptr; 394 | state->identifier.length = length; 395 | 396 | return TOK_IDENTIFIER; 397 | } 398 | 399 | sql3token_t sql3lexer_escape (sql3state *state) { 400 | sql3char c, escaped = NEXT; // consume escaped char 401 | if (escaped == '[') escaped = ']'; // mysql compatibility mode 402 | 403 | // read until EOF or closing escape character 404 | size_t offset = state->offset; 405 | do { 406 | c = NEXT; 407 | } while ((c != 0) && (c != escaped)); 408 | 409 | const char *ptr = &state->buffer[offset]; 410 | size_t length = state->offset - (offset + 1); 411 | 412 | // sanity check on closing escaped character 413 | if (c != escaped) return TOK_ERROR; 414 | 415 | // setup internal identifier 416 | state->identifier.ptr = ptr; 417 | state->identifier.length = length; 418 | 419 | return TOK_IDENTIFIER; 420 | } 421 | 422 | static bool sql3lexer_checkskip (sql3state *state) { 423 | sql3char c; 424 | loop: 425 | c = PEEK; 426 | if (symbol_is_toskip(c)) {SKIP_ONE; goto loop;} 427 | if (symbol_is_comment(c, state)) {if (sql3lexer_comment(state) != TOK_COMMENT) return false; goto loop;} 428 | 429 | return true; 430 | } 431 | 432 | static sql3token_t sql3lexer_next (sql3state *state) { 433 | loop: 434 | if (IS_EOF) return TOK_EOF; 435 | sql3char c = PEEK; 436 | if (c == 0) return TOK_EOF; 437 | 438 | if (symbol_is_toskip(c)) {SKIP_ONE; goto loop;} 439 | if (symbol_is_comment(c, state)) {if (sql3lexer_comment(state) != TOK_COMMENT) return TOK_ERROR; goto loop;} 440 | if (symbol_is_punctuation(c)) return sql3lexer_punctuation(state); 441 | if (symbol_is_alpha(c)) return sql3lexer_alpha(state); 442 | if (symbol_is_escape(c)) return sql3lexer_escape(state); 443 | 444 | return TOK_ERROR; 445 | } 446 | 447 | static sql3token_t sql3lexer_peek (sql3state *state) { 448 | // peek calls sql3lexer_next and reset its state after the call 449 | size_t saved = state->offset; 450 | sql3token_t token = sql3lexer_next(state); 451 | state->offset = saved; 452 | 453 | return token; 454 | } 455 | 456 | // MARK: - Internal Parser - 457 | 458 | static sql3error_code sql3parse_optionalorder (sql3state *state, sql3order_clause *clause) { 459 | sql3token_t token = sql3lexer_peek(state); 460 | *clause = SQL3ORDER_NONE; 461 | 462 | if ((token == TOK_ASC) || (token == TOK_DESC)) { 463 | sql3lexer_next(state); // consume token 464 | if (token == TOK_ASC) *clause = SQL3ORDER_ASC; 465 | else *clause = SQL3ORDER_DESC; 466 | } 467 | 468 | return SQL3ERROR_NONE; 469 | } 470 | 471 | static sql3error_code sql3parse_optionalconflitclause (sql3state *state, sql3conflict_clause *conflict) { 472 | sql3token_t token = sql3lexer_peek(state); 473 | *conflict = SQL3CONFLICT_NONE; 474 | 475 | if (token == TOK_ON) { 476 | sql3lexer_next(state); // consume TOK_ON 477 | 478 | token = sql3lexer_next(state); 479 | if (token != TOK_CONFLICT) return SQL3ERROR_SYNTAX; 480 | 481 | token = sql3lexer_next(state); 482 | if (token == TOK_ROLLBACK) *conflict = SQL3CONFLICT_ROLLBACK; 483 | else if (token == TOK_ABORT) *conflict = SQL3CONFLICT_ABORT; 484 | else if (token == TOK_FAIL) *conflict = SQL3CONFLICT_FAIL; 485 | else if (token == TOK_IGNORE) *conflict = SQL3CONFLICT_IGNORE; 486 | else if (token == TOK_REPLACE) *conflict = SQL3CONFLICT_REPLACE; 487 | else return SQL3ERROR_SYNTAX; 488 | } 489 | 490 | return SQL3ERROR_NONE; 491 | } 492 | 493 | static sql3foreignkey *sql3parse_foreignkey_clause (sql3state *state) { 494 | sql3foreignkey *fk = SQL3MALLOC0(sizeof(sql3foreignkey)); 495 | if (!fk) return NULL; 496 | 497 | // parse foreign table name 498 | sql3token_t token = sql3lexer_next(state); 499 | if (token != TOK_IDENTIFIER) goto error; 500 | fk->table = state->identifier; 501 | 502 | // check for optional columns part 503 | if (sql3lexer_peek(state) == TOK_OPEN_PARENTHESIS) { 504 | sql3lexer_next(state); // consume TOK_OPEN_PARENTHESIS 505 | 506 | // parse column names 507 | do { 508 | // parse column-name 509 | token = sql3lexer_next(state); 510 | if (token != TOK_IDENTIFIER) goto error; 511 | 512 | // add column name 513 | ++fk->num_columns; 514 | fk->column_name = SQL3REALLOC(fk->column_name, sizeof(sql3string) * fk->num_columns); 515 | if (!fk->column_name) goto error; 516 | fk->column_name[fk->num_columns-1] = state->identifier; 517 | 518 | token = sql3lexer_peek(state); 519 | if (token == TOK_COMMA) sql3lexer_next(state); // consume TOK_COMMA 520 | 521 | } while (token == TOK_COMMA); 522 | 523 | // closed parenthesis is mandatory here 524 | if (sql3lexer_next(state) != TOK_CLOSED_PARENTHESIS) goto error; 525 | } 526 | 527 | // check for optional part 528 | fk_loop: 529 | token = sql3lexer_peek(state); 530 | if ((token == TOK_ON) || (token == TOK_MATCH) || (token == TOK_NOT) || (token == TOK_DEFERRABLE)) { 531 | sql3lexer_next(state); // consume token 532 | 533 | if (token == TOK_MATCH) { 534 | token = sql3lexer_next(state); 535 | if (token != TOK_IDENTIFIER) goto error; 536 | fk->match = state->identifier; 537 | goto fk_loop; 538 | } 539 | 540 | if (token == TOK_ON) { 541 | token = sql3lexer_next(state); 542 | if ((token != TOK_DELETE) && (token != TOK_UPDATE)) goto error; 543 | bool isupdate = (token == TOK_UPDATE); 544 | 545 | token = sql3lexer_next(state); 546 | if (token == TOK_CASCADE) { 547 | if (isupdate) fk->on_update = SQL3FKACTION_CASCADE; 548 | else fk->on_delete = SQL3FKACTION_CASCADE; 549 | } else if (token == TOK_RESTRICT) { 550 | if (isupdate) fk->on_update = SQL3FKACTION_RESTRICT; 551 | else fk->on_delete = SQL3FKACTION_RESTRICT; 552 | } else if (token == TOK_SET) { 553 | token = sql3lexer_next(state); 554 | if ((token != TOK_NULL) && (token != TOK_DEFAULT)) goto error; 555 | if (token == TOK_NULL) { 556 | if (isupdate) fk->on_update = SQL3FKACTION_SETNULL; 557 | else fk->on_delete = SQL3FKACTION_SETNULL; 558 | } else { 559 | // TOK_DEFAULT 560 | if (isupdate) fk->on_update = SQL3FKACTION_SETDEFAULT; 561 | else fk->on_delete = SQL3FKACTION_SETDEFAULT; 562 | } 563 | } else if (token == TOK_NO) { 564 | if (sql3lexer_next(state) != TOK_ACTION) goto error; 565 | if (isupdate) fk->on_update = SQL3FKACTION_NOACTION; 566 | else fk->on_delete = SQL3FKACTION_NOACTION; 567 | } 568 | goto fk_loop; 569 | } 570 | 571 | bool isnot = false; 572 | if (token == TOK_NOT) { 573 | token = sql3lexer_next(state); // get next token 574 | isnot = true; 575 | } 576 | 577 | if (token == TOK_DEFERRABLE) { 578 | fk->deferrable = (isnot) ? SQL3DEFTYPE_NOTDEFERRABLE : SQL3DEFTYPE_DEFERRABLE; 579 | if (sql3lexer_peek(state) == TOK_INITIALLY) { 580 | sql3lexer_next(state); // consume TOK_INITIALLY 581 | token = sql3lexer_next(state); // get next token 582 | if (token == TOK_DEFERRED) { 583 | fk->deferrable = (isnot) ? SQL3DEFTYPE_NOTDEFERRABLE_INITIALLY_DEFERRED : SQL3DEFTYPE_DEFERRABLE_INITIALLY_DEFERRED; 584 | } else if (token == TOK_IMMEDIATE) { 585 | fk->deferrable = (isnot) ? SQL3DEFTYPE_NOTDEFERRABLE_INITIALLY_IMMEDIATE : SQL3DEFTYPE_DEFERRABLE_INITIALLY_IMMEDIATE; 586 | } else goto error; 587 | } 588 | goto fk_loop; 589 | } 590 | goto error; 591 | } 592 | 593 | return fk; 594 | 595 | error: 596 | if (fk) SQL3FREE(fk); 597 | return NULL; 598 | } 599 | 600 | static sql3error_code sql3parse_table_options (sql3state *state) { 601 | sql3token_t token = sql3lexer_peek(state); 602 | 603 | while (1) { 604 | if (token == TOK_WITHOUT) { 605 | // WITHOUT ROWID table option 606 | 607 | // consume WITHOUT 608 | sql3lexer_next(state); 609 | 610 | // ROWID is mandatory at this point 611 | if (sql3lexer_next(state) != TOK_ROWID) return SQL3ERROR_SYNTAX; 612 | 613 | // set without rowid flag 614 | state->table->is_withoutrowid = true; 615 | } else if (token == TOK_STRICT) { 616 | // STRICT table option 617 | 618 | // consume STRICT 619 | sql3lexer_next(state); 620 | 621 | // set without rowid flag 622 | state->table->is_strict = true; 623 | } else { 624 | return SQL3ERROR_NONE; 625 | } 626 | 627 | // check for optional , 628 | token = sql3lexer_peek(state); 629 | if (token != TOK_COMMA) break; 630 | 631 | // consume COMMA and PEEK next token 632 | sql3lexer_next(state); 633 | token = sql3lexer_peek(state); 634 | continue; 635 | } 636 | 637 | return SQL3ERROR_NONE; 638 | } 639 | 640 | static sql3tableconstraint *sql3parse_table_constraint (sql3state *state) { 641 | sql3token_t token = sql3lexer_peek(state); 642 | sql3tableconstraint *constraint = (sql3tableconstraint *)SQL3MALLOC0(sizeof(sql3tableconstraint)); 643 | if (!constraint) return NULL; 644 | 645 | // optional constraint name 646 | if (token == TOK_CONSTRAINT) { 647 | sql3lexer_next(state); // consume token 648 | token = sql3lexer_next(state); 649 | if (token != TOK_IDENTIFIER) goto error; 650 | constraint->name = state->identifier; 651 | 652 | // peek next token 653 | token = sql3lexer_peek(state); 654 | 655 | // sanity check next token 656 | if ((token != TOK_CHECK) && (token != TOK_PRIMARY) && (token != TOK_UNIQUE) && (token != TOK_FOREIGN)) goto error; 657 | } 658 | 659 | // check for others constraint 660 | if (token == TOK_CHECK) { 661 | token = sql3lexer_next(state); // consume token 662 | constraint->type = SQL3TABLECONSTRAINT_CHECK; 663 | 664 | // expressions are not supported in this version 665 | goto error; 666 | } 667 | // same code to execute for PRIMARY KEY or UNIQUE constraint 668 | else if ((token == TOK_PRIMARY) || (token == TOK_UNIQUE)) { 669 | token = sql3lexer_next(state); // consume token 670 | if (token == TOK_PRIMARY) { 671 | if (sql3lexer_next(state) != TOK_KEY) goto error; 672 | constraint->type = SQL3TABLECONSTRAINT_PRIMARYKEY; 673 | } else constraint->type = SQL3TABLECONSTRAINT_UNIQUE; 674 | 675 | if (sql3lexer_next(state) != TOK_OPEN_PARENTHESIS) goto error; 676 | 677 | // get indexed column 678 | do { 679 | sql3idxcolumn column = {0}; 680 | 681 | // parse column-name 682 | token = sql3lexer_next(state); 683 | if (token != TOK_IDENTIFIER) goto error; 684 | column.name = state->identifier; 685 | 686 | if (sql3lexer_peek(state) == TOK_COLLATE) { 687 | sql3lexer_next(state); // consume TOK_COLLATE 688 | 689 | // parse collation-name 690 | token = sql3lexer_next(state); 691 | if (token != TOK_IDENTIFIER) goto error; 692 | column.collate_name = state->identifier; 693 | } 694 | 695 | // parse optional order 696 | if (sql3parse_optionalorder(state, &column.order) != SQL3ERROR_NONE) goto error; 697 | 698 | // add indexed column 699 | ++constraint->num_indexed; 700 | constraint->indexed_columns = SQL3REALLOC(constraint->indexed_columns, sizeof(sql3idxcolumn) * constraint->num_indexed); 701 | if (!constraint->indexed_columns) goto error; 702 | constraint->indexed_columns[constraint->num_indexed-1] = column; 703 | 704 | token = sql3lexer_peek(state); 705 | if (token == TOK_COMMA) sql3lexer_next(state); // consume TOK_COMMA 706 | 707 | } while (token == TOK_COMMA); 708 | 709 | if (sql3lexer_next(state) != TOK_CLOSED_PARENTHESIS) goto error; 710 | if (sql3parse_optionalconflitclause(state, &constraint->conflict_clause) != SQL3ERROR_NONE) goto error; 711 | } 712 | // foreign key constraint 713 | else if (token == TOK_FOREIGN) { 714 | sql3lexer_next(state); // consume TOK_FOREIGN 715 | if (sql3lexer_next(state) != TOK_KEY) goto error; 716 | if (sql3lexer_next(state) != TOK_OPEN_PARENTHESIS) goto error; 717 | 718 | constraint->type = SQL3TABLECONSTRAINT_FOREIGNKEY; 719 | 720 | // get column names 721 | do { 722 | // parse column-name 723 | token = sql3lexer_next(state); 724 | if (token != TOK_IDENTIFIER) goto error; 725 | 726 | // add column name 727 | ++constraint->foreignkey_num; 728 | constraint->foreignkey_name = SQL3REALLOC(constraint->foreignkey_name, sizeof(sql3string) * constraint->foreignkey_num); 729 | if (!constraint->foreignkey_name) goto error; 730 | constraint->foreignkey_name[constraint->foreignkey_num-1] = state->identifier; 731 | 732 | token = sql3lexer_peek(state); 733 | if (token == TOK_COMMA) sql3lexer_next(state); // consume TOK_COMMA 734 | 735 | } while (token == TOK_COMMA); 736 | 737 | if (sql3lexer_next(state) != TOK_CLOSED_PARENTHESIS) goto error; 738 | if (sql3lexer_next(state) != TOK_REFERENCES) goto error; 739 | 740 | // parse foreign key clause 741 | sql3foreignkey *fk = sql3parse_foreignkey_clause(state); 742 | if (!fk) goto error; 743 | constraint->foreignkey_clause = fk; 744 | } 745 | 746 | return constraint; 747 | 748 | error: 749 | if (constraint) SQL3FREE(constraint); 750 | return NULL; 751 | } 752 | 753 | static sql3string sql3parse_literal (sql3state *state) { 754 | // signed-number (+/-) => numeric-literal 755 | // literal-value 756 | // numeric-literal 757 | // string-literal 758 | // blob-literal (x/X'') 759 | // NULL 760 | // TRUE 761 | // FALSE 762 | // CURRENT_TIME 763 | // CURRENT_DATE 764 | // CURRENT_TIMESTAMP 765 | 766 | sql3lexer_checkskip(state); 767 | 768 | size_t offset = state->offset; 769 | sql3char c = NEXT; 770 | if (c == '\'' || c == '"') { 771 | // parse string literal 772 | sql3char escaped = c; 773 | while (true) { 774 | c = NEXT; 775 | if (c == escaped) { 776 | sql3char c2 = PEEK; 777 | if (c2 != escaped) break; 778 | (void) NEXT; 779 | } 780 | } 781 | } else { 782 | // parse everything else up until a space 783 | while (true) { 784 | c = PEEK; 785 | if (c == ' ' || c == ',' || c == ')') break; 786 | c = NEXT; 787 | } 788 | } 789 | 790 | const char *ptr = &state->buffer[offset]; 791 | size_t length = state->offset - offset; 792 | 793 | sql3string result = {ptr, length}; 794 | return result; 795 | } 796 | 797 | static sql3string sql3parse_expression (sql3state *state) { 798 | // '(' expression ')' 799 | 800 | sql3lexer_checkskip(state); 801 | 802 | size_t offset = state->offset; 803 | sql3char c = NEXT; // '(' 804 | uint32_t count = 1; // count number of '(' 805 | 806 | while (true) { 807 | c = NEXT; 808 | if (c == '(') ++count; 809 | else if (c == ')') { 810 | if (--count == 0) break; 811 | } 812 | } 813 | 814 | const char *ptr = &state->buffer[offset]; 815 | size_t length = state->offset - offset; 816 | 817 | sql3string result = {ptr, length}; 818 | return result; 819 | } 820 | 821 | static sql3error_code sql3parse_column_type (sql3state *state, sql3column *column) { 822 | // column type is reported as a string 823 | size_t offset = 0; 824 | while (sql3lexer_peek(state) == TOK_IDENTIFIER) { 825 | // consume identifier 826 | sql3lexer_next(state); 827 | 828 | // mark the beginning of the first identifier 829 | if (offset == 0) offset = state->offset - state->identifier.length; 830 | } 831 | const char *ptr = &state->buffer[offset]; 832 | size_t length = state->offset - offset; 833 | 834 | // setup internal identifier 835 | column->type.ptr = ptr; 836 | column->type.length = length; 837 | 838 | // check for optional lenght 839 | if (sql3lexer_peek(state) == TOK_OPEN_PARENTHESIS) { 840 | sql3lexer_next(state); // consume '(' 841 | 842 | // mark start of string 843 | offset = state->offset; 844 | sql3char c; 845 | do { 846 | c = NEXT; 847 | } while ((c != 0) && (c != ')')); 848 | 849 | // sanity check on closing escaped character 850 | if (c != ')') return SQL3ERROR_SYNTAX; 851 | 852 | // don't include ')' in column lenght 853 | ptr = &state->buffer[offset]; 854 | length = state->offset - (offset + 1); 855 | 856 | column->length.ptr = ptr; 857 | column->length.length = length; 858 | } 859 | 860 | return SQL3ERROR_NONE; 861 | } 862 | 863 | static sql3error_code sql3parse_column_constraints (sql3state *state, sql3column *column) { 864 | while (token_is_column_constraint(sql3lexer_peek(state))) { 865 | sql3token_t token = sql3lexer_next(state); 866 | 867 | // optional constraint name 868 | if (token == TOK_CONSTRAINT) { 869 | token = sql3lexer_next(state); 870 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 871 | column->constraint_name = state->identifier; 872 | token = sql3lexer_next(state); 873 | } 874 | 875 | switch (token) { 876 | case TOK_PRIMARY: 877 | token = sql3lexer_next(state); 878 | if (token != TOK_KEY) return SQL3ERROR_SYNTAX; 879 | column->is_primarykey = true; 880 | if (sql3parse_optionalorder(state, &column->pk_order) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; 881 | if (sql3parse_optionalconflitclause(state, &column->pk_conflictclause) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; 882 | if (sql3lexer_peek(state) == TOK_AUTOINCREMENT) { 883 | sql3lexer_next(state); // consume TOK_AUTOINCREMENT 884 | column->is_autoincrement = true; 885 | } 886 | break; 887 | 888 | case TOK_NOT: 889 | token = sql3lexer_next(state); 890 | if (token != TOK_NULL) return SQL3ERROR_SYNTAX; 891 | column->is_notnull = true; 892 | if (sql3parse_optionalconflitclause(state, &column->notnull_conflictclause) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; 893 | break; 894 | 895 | case TOK_UNIQUE: 896 | column->is_unique = true; 897 | if (sql3parse_optionalconflitclause(state, &column->unique_conflictclause) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; 898 | break; 899 | 900 | case TOK_CHECK: 901 | column->check_expr = sql3parse_expression(state); 902 | break; 903 | 904 | case TOK_DEFAULT: 905 | // signed-number (+/-) => numeric-literal 906 | // literal-value 907 | // numeric-literal 908 | // string-literal 909 | // blob-literal (x/X'') 910 | // NULL 911 | // TRUE 912 | // FALSE 913 | // CURRENT_TIME 914 | // CURRENT_DATE 915 | // CURRENT_TIMESTAMP 916 | // '(' expression ')' 917 | 918 | // expressions are not supported in this version 919 | if (sql3lexer_peek(state) == TOK_OPEN_PARENTHESIS) column->default_expr = sql3parse_expression(state); 920 | else column->default_expr = sql3parse_literal(state); 921 | break; 922 | 923 | case TOK_COLLATE: 924 | token = sql3lexer_next(state); 925 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 926 | column->collate_name = state->identifier; 927 | break; 928 | 929 | case TOK_REFERENCES: { 930 | sql3foreignkey *fk = sql3parse_foreignkey_clause(state); 931 | if (!fk) return SQL3ERROR_SYNTAX; 932 | column->foreignkey_clause = fk; 933 | } break; 934 | 935 | default: 936 | return SQL3ERROR_SYNTAX; 937 | } 938 | } 939 | 940 | return SQL3ERROR_NONE; 941 | } 942 | 943 | static sql3column *sql3parse_column (sql3state *state) { 944 | sql3column *column = SQL3MALLOC0(sizeof(sql3column)); 945 | if (!column) return NULL; 946 | 947 | // set column comment reference inside state context 948 | state->comment = &column->comment; 949 | 950 | // column name is mandatory 951 | sql3token_t token = sql3lexer_next(state); 952 | if (token != TOK_IDENTIFIER) goto error; 953 | 954 | // copy column name 955 | column->name = state->identifier; 956 | 957 | // parse optional column type 958 | if (sql3lexer_peek(state) == TOK_IDENTIFIER) { 959 | if (sql3parse_column_type(state, column) != SQL3ERROR_NONE) goto error; 960 | } 961 | 962 | // check optional column constraints path 963 | if (token_is_column_constraint(sql3lexer_peek(state))) { 964 | if (sql3parse_column_constraints(state, column) != SQL3ERROR_NONE) goto error; 965 | } 966 | 967 | return column; 968 | 969 | error: 970 | if (column) SQL3FREE(column); 971 | return NULL; 972 | } 973 | 974 | // MARK: - 975 | 976 | static sql3error_code sql3parse_schema_identifier (sql3state *state) { 977 | sql3table *table = state->table; 978 | 979 | // at this point there should be an identifier 980 | // only the optional dot will tell if it is a schema or a table name 981 | sql3token_t token = sql3lexer_next(state); 982 | 983 | // temp is a keyword but it is perfectly legal as a schema name 984 | if (token != TOK_IDENTIFIER && token != TOK_TEMP) return SQL3ERROR_SYNTAX; 985 | 986 | const char *identifier = (token == TOK_IDENTIFIER) ? sql3string_ptr(&state->identifier, NULL) : temp_identifier.ptr; 987 | if (!identifier) return SQL3ERROR_SYNTAX; 988 | 989 | // check for optional DOT (if any then identifier is a schema name) 990 | if (sql3lexer_peek(state) == TOK_DOT) { 991 | // consume DOT 992 | sql3lexer_next(state); 993 | 994 | // set schema name 995 | table->schema = (token == TOK_IDENTIFIER) ? state->identifier : temp_identifier; 996 | 997 | // parse table name 998 | if (sql3lexer_next(state) != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 999 | identifier = sql3string_ptr(&state->identifier, NULL); 1000 | if (!identifier) return SQL3ERROR_SYNTAX; 1001 | } 1002 | 1003 | // set table name 1004 | table->name = state->identifier; 1005 | 1006 | return SQL3ERROR_NONE; 1007 | } 1008 | 1009 | static sql3error_code sql3parse_alter (sql3state *state) { 1010 | // https://www.sqlite.org/lang_altertable.html 1011 | // ALTER TABLE [schema-name .]table-name ... 1012 | 1013 | sql3table *table = state->table; 1014 | 1015 | // next statement after an ALTER must be TABLE 1016 | sql3token_t token = sql3lexer_next(state); 1017 | if (token != TOK_TABLE) return SQL3ERROR_UNSUPPORTEDSQL; 1018 | 1019 | // parse [schema.]name 1020 | sql3error_code err = sql3parse_schema_identifier(state); 1021 | if (err != SQL3ERROR_NONE) return err; 1022 | 1023 | // next token will tell us the alter table statment type 1024 | token = sql3lexer_next(state); 1025 | 1026 | // RENAME TO new-table-name 1027 | // RENAME [COLUMN] column-name TO new-column-name 1028 | // ADD [COLUMN] column-def 1029 | // DROP [COLUMN] column-name 1030 | 1031 | sql3token_t next = sql3lexer_peek(state); 1032 | 1033 | switch (token) { 1034 | case TOK_RENAME: 1035 | if (next == TOK_TO) { 1036 | sql3lexer_next(state); 1037 | table->type = SQL3ALTER_RENAME_TABLE; 1038 | 1039 | // new-table-name is mandatory 1040 | token = sql3lexer_next(state); 1041 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 1042 | 1043 | // copy new table name 1044 | table->new_name = state->identifier; 1045 | 1046 | } else { 1047 | if (next == TOK_COLUMN) sql3lexer_next(state); 1048 | table->type = SQL3ALTER_RENAME_COLUMN; 1049 | 1050 | // column-name is mandatory 1051 | token = sql3lexer_next(state); 1052 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 1053 | 1054 | // copy current column name 1055 | table->current_name = state->identifier; 1056 | 1057 | // parse TO 1058 | token = sql3lexer_next(state); 1059 | if (token != TOK_TO) return SQL3ERROR_SYNTAX; 1060 | 1061 | // copy new column name 1062 | table->new_name = state->identifier; 1063 | } 1064 | break; 1065 | 1066 | case TOK_ADD: 1067 | if (next == TOK_COLUMN) sql3lexer_next(state); 1068 | table->type = SQL3ALTER_ADD_COLUMN; 1069 | 1070 | // column-name is mandatory 1071 | token = sql3lexer_peek(state); 1072 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 1073 | 1074 | // parse column definition 1075 | sql3column *column = sql3parse_column(state); 1076 | if (!column) return SQL3ERROR_SYNTAX; 1077 | 1078 | // add column to columns array 1079 | ++table->num_columns; 1080 | table->columns = SQL3REALLOC(table->columns, sizeof(sql3column**) * table->num_columns); 1081 | if (!table->columns) return SQL3ERROR_MEMORY; 1082 | table->columns[table->num_columns-1] = column; 1083 | 1084 | break; 1085 | 1086 | case TOK_DROP: 1087 | if (next == TOK_COLUMN) sql3lexer_next(state); 1088 | table->type = SQL3ALTER_DROP_COLUMN; 1089 | 1090 | // column-name is mandatory 1091 | token = sql3lexer_next(state); 1092 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 1093 | 1094 | // copy current column name 1095 | table->current_name = state->identifier; 1096 | 1097 | break; 1098 | 1099 | default: 1100 | return SQL3ERROR_UNSUPPORTEDSQL; 1101 | } 1102 | 1103 | // check and consume optional ; 1104 | token = sql3lexer_peek(state); 1105 | if (token == TOK_SEMICOLON) sql3lexer_next(state); 1106 | 1107 | return SQL3ERROR_NONE; 1108 | } 1109 | 1110 | static sql3error_code sql3parse_create (sql3state *state) { 1111 | // https://www.sqlite.org/lang_createtable.html 1112 | // CREATE [TEMP | TEMPORARY] TABLE [IF NOT EXISTS] [schema-name .]table-name ... 1113 | 1114 | sql3table *table = state->table; 1115 | 1116 | // set statement type 1117 | table->type = SQL3CREATE_TABLE; 1118 | 1119 | // next statement after a CREATE can be TEMP or a TABLE 1120 | sql3token_t token = sql3lexer_next(state); 1121 | if (token == TOK_TEMP) { 1122 | table->is_temporary = true; 1123 | 1124 | // parse next token (must be TABLE token) 1125 | token = sql3lexer_next(state); 1126 | } 1127 | 1128 | // assure TABLE token 1129 | if (token != TOK_TABLE) return SQL3ERROR_UNSUPPORTEDSQL; 1130 | 1131 | // check for IF NOT EXISTS clause 1132 | if (sql3lexer_peek(state) == TOK_IF) { 1133 | // consume IF 1134 | sql3lexer_next(state); 1135 | 1136 | // next must be NOT 1137 | if (sql3lexer_next(state) != TOK_NOT) return SQL3ERROR_SYNTAX; 1138 | 1139 | // next must be EXISTS 1140 | if (sql3lexer_next(state) != TOK_EXISTS) return SQL3ERROR_SYNTAX; 1141 | 1142 | // safely set the flag here 1143 | table->is_ifnotexists = true; 1144 | } 1145 | 1146 | // parse [schema.]name 1147 | sql3error_code err = sql3parse_schema_identifier(state); 1148 | if (err != SQL3ERROR_NONE) return err; 1149 | 1150 | // start parsing column and table constraints 1151 | 1152 | // '(' is mandatory here 1153 | token = sql3lexer_next(state); 1154 | if (token == TOK_AS) return SQL3ERROR_UNSUPPORTEDSQL; 1155 | if (token != TOK_OPEN_PARENTHESIS) return SQL3ERROR_SYNTAX; 1156 | 1157 | // parse column-def 1158 | while (1) { 1159 | token = sql3lexer_peek(state); 1160 | 1161 | // column name is mandatory here 1162 | if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; 1163 | 1164 | // parse column definition 1165 | sql3column *column = sql3parse_column(state); 1166 | if (!column) return SQL3ERROR_SYNTAX; 1167 | 1168 | // add column to columns array 1169 | ++table->num_columns; 1170 | table->columns = SQL3REALLOC(table->columns, sizeof(sql3column**) * table->num_columns); 1171 | if (!table->columns) return SQL3ERROR_MEMORY; 1172 | table->columns[table->num_columns-1] = column; 1173 | 1174 | // check for optional comma 1175 | token = sql3lexer_peek(state); 1176 | if (token == TOK_COMMA) { 1177 | sql3lexer_next(state); // consume comma 1178 | token = sql3lexer_peek(state); // peek next token 1179 | 1180 | if (token_is_table_constraint(token)) break; 1181 | else continue; 1182 | } 1183 | 1184 | if (token == TOK_CLOSED_PARENTHESIS) { 1185 | //sql3lexer_next(state); // consume closed parenthesis 1186 | token = sql3lexer_peek(state); 1187 | break; 1188 | } 1189 | 1190 | // if it is not an identifier -> column, a comma, a token_table_constraint 1191 | // nor a closed_parenthesis then it is a syntax error 1192 | return SQL3ERROR_SYNTAX; 1193 | } 1194 | 1195 | // parse optional table-constraint 1196 | while (token_is_table_constraint(token)) { 1197 | state->comment = &table->comment; 1198 | 1199 | sql3tableconstraint *constraint = sql3parse_table_constraint(state); 1200 | if (!constraint) return SQL3ERROR_SYNTAX; 1201 | 1202 | // add column to columns array 1203 | ++table->num_constraint; 1204 | table->constraints = SQL3REALLOC(table->constraints, sizeof(sql3tableconstraint**) * table->num_constraint); 1205 | if (!table->constraints) return SQL3ERROR_MEMORY; 1206 | table->constraints[table->num_constraint-1] = constraint; 1207 | 1208 | // check for optional comma 1209 | if (sql3lexer_peek(state) == TOK_COMMA) { 1210 | sql3lexer_next(state); // consume comma 1211 | token = sql3lexer_peek(state); // peek next token 1212 | continue; 1213 | } 1214 | 1215 | if (sql3lexer_peek(state) == TOK_CLOSED_PARENTHESIS) break; 1216 | 1217 | // if it is not a token_table_constraint nor a closed_parenthesis then it is a syntax error 1218 | return SQL3ERROR_SYNTAX; 1219 | } 1220 | 1221 | // ')' is mandatory here 1222 | token = sql3lexer_next(state); 1223 | if (token != TOK_CLOSED_PARENTHESIS) return SQL3ERROR_SYNTAX; 1224 | 1225 | // set table comment reference inside state context 1226 | state->comment = &table->comment; 1227 | 1228 | // check for optional TABLE options (WITHOUT ROWID and/or STRICT) 1229 | if (sql3parse_table_options(state) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; 1230 | 1231 | // check and consume optional ; 1232 | token = sql3lexer_peek(state); 1233 | if (token == TOK_SEMICOLON) sql3lexer_next(state); 1234 | 1235 | return SQL3ERROR_NONE; 1236 | } 1237 | 1238 | static sql3error_code sql3parse (sql3state *state) { 1239 | // take a decision based on first token (CREATE or ALTER statements) 1240 | sql3token_t token = sql3lexer_next(state); 1241 | 1242 | if (token == TOK_CREATE) return sql3parse_create(state); 1243 | if (token == TOK_ALTER) return sql3parse_alter(state); 1244 | 1245 | return SQL3ERROR_UNSUPPORTEDSQL; 1246 | } 1247 | 1248 | //MARK: - Public Table Functions - 1249 | 1250 | sql3string *sql3table_schema (sql3table *table) { 1251 | CHECK_STR(table->schema); 1252 | return &table->schema; 1253 | } 1254 | 1255 | sql3string *sql3table_name (sql3table *table) { 1256 | CHECK_STR(table->name); 1257 | return &table->name; 1258 | } 1259 | 1260 | sql3string *sql3table_comment (sql3table *table) { 1261 | CHECK_STR(table->comment); 1262 | return &table->comment; 1263 | } 1264 | 1265 | sql3string *sql3table_current_name (sql3table *table) { 1266 | CHECK_STR(table->current_name); 1267 | return &table->current_name; 1268 | } 1269 | 1270 | sql3string *sql3table_new_name (sql3table *table) { 1271 | CHECK_STR(table->new_name); 1272 | return &table->new_name; 1273 | } 1274 | 1275 | bool sql3table_is_temporary (sql3table *table) { 1276 | return table->is_temporary; 1277 | } 1278 | 1279 | bool sql3table_is_ifnotexists (sql3table *table) { 1280 | return table->is_ifnotexists; 1281 | } 1282 | 1283 | bool sql3table_is_withoutrowid (sql3table *table) { 1284 | return table->is_withoutrowid; 1285 | } 1286 | 1287 | bool sql3table_is_strict (sql3table *table) { 1288 | return table->is_strict; 1289 | } 1290 | 1291 | size_t sql3table_num_columns (sql3table *table) { 1292 | return table->num_columns; 1293 | } 1294 | 1295 | sql3column *sql3table_get_column (sql3table *table, size_t index) { 1296 | CHECK_IDX(index, table->num_columns); 1297 | return table->columns[index]; 1298 | } 1299 | 1300 | size_t sql3table_num_constraints (sql3table *table) { 1301 | return table->num_constraint; 1302 | } 1303 | 1304 | sql3tableconstraint *sql3table_get_constraint (sql3table *table, size_t index) { 1305 | CHECK_IDX(index, table->num_constraint); 1306 | return table->constraints[index]; 1307 | } 1308 | 1309 | sql3statement_type sql3table_type (sql3table *table) { 1310 | return table->type; 1311 | } 1312 | 1313 | void sql3table_free (sql3table *table) { 1314 | if (!table) return; 1315 | 1316 | // free columns 1317 | for (size_t i=0; inum_columns; ++i) { 1318 | sql3column *column = table->columns[i]; 1319 | if (column->foreignkey_clause) { 1320 | if (column->foreignkey_clause->column_name) SQL3FREE(column->foreignkey_clause->column_name); 1321 | SQL3FREE(column->foreignkey_clause); 1322 | } 1323 | SQL3FREE(column); 1324 | } 1325 | if (table->columns) SQL3FREE(table->columns); 1326 | 1327 | // free table constraints 1328 | for (size_t i=0; inum_constraint; ++i) { 1329 | sql3tableconstraint *constraint = table->constraints[i]; 1330 | if ((constraint->type == SQL3TABLECONSTRAINT_PRIMARYKEY) || (constraint->type == SQL3TABLECONSTRAINT_UNIQUE)) { 1331 | if (constraint->indexed_columns) SQL3FREE(constraint->indexed_columns); 1332 | } else if (constraint->type == SQL3TABLECONSTRAINT_FOREIGNKEY) { 1333 | if (constraint->foreignkey_name) SQL3FREE(constraint->foreignkey_name); 1334 | if (constraint->foreignkey_clause) { 1335 | if (constraint->foreignkey_clause->column_name) SQL3FREE(constraint->foreignkey_clause->column_name); 1336 | SQL3FREE(constraint->foreignkey_clause); 1337 | } 1338 | } 1339 | SQL3FREE(constraint); 1340 | } 1341 | if (table->constraints) SQL3FREE(table->constraints); 1342 | 1343 | SQL3FREE(table); 1344 | } 1345 | 1346 | // MARK: - Public Table Constraint Functions - 1347 | 1348 | sql3string *sql3table_constraint_name (sql3tableconstraint *tconstraint) { 1349 | CHECK_STR(tconstraint->name); 1350 | return &tconstraint->name; 1351 | } 1352 | 1353 | sql3constraint_type sql3table_constraint_type (sql3tableconstraint *tconstraint) { 1354 | return tconstraint->type; 1355 | } 1356 | 1357 | size_t sql3table_constraint_num_idxcolumns (sql3tableconstraint *tconstraint) { 1358 | if ((tconstraint->type != SQL3TABLECONSTRAINT_PRIMARYKEY) && (tconstraint->type != SQL3TABLECONSTRAINT_UNIQUE)) return 0; 1359 | return tconstraint->num_indexed; 1360 | } 1361 | 1362 | sql3idxcolumn *sql3table_constraint_get_idxcolumn (sql3tableconstraint *tconstraint, size_t index) { 1363 | if ((tconstraint->type != SQL3TABLECONSTRAINT_PRIMARYKEY) && (tconstraint->type != SQL3TABLECONSTRAINT_UNIQUE)) return NULL; 1364 | CHECK_IDX(index, tconstraint->num_indexed); 1365 | return &tconstraint->indexed_columns[index]; 1366 | } 1367 | 1368 | sql3conflict_clause sql3table_constraint_conflict_clause (sql3tableconstraint *tconstraint) { 1369 | if ((tconstraint->type != SQL3TABLECONSTRAINT_PRIMARYKEY) && (tconstraint->type != SQL3TABLECONSTRAINT_UNIQUE)) return SQL3CONFLICT_NONE; 1370 | return tconstraint->conflict_clause; 1371 | } 1372 | 1373 | sql3string *sql3table_constraint_check_expr (sql3tableconstraint *tconstraint) { 1374 | if (tconstraint->type != SQL3TABLECONSTRAINT_CHECK) return NULL; 1375 | CHECK_STR(tconstraint->check_expr); 1376 | return &tconstraint->check_expr; 1377 | } 1378 | 1379 | size_t sql3table_constraint_num_fkcolumns (sql3tableconstraint *tconstraint) { 1380 | if (tconstraint->type != SQL3TABLECONSTRAINT_FOREIGNKEY) return 0; 1381 | return tconstraint->foreignkey_num; 1382 | } 1383 | 1384 | sql3string *sql3table_constraint_get_fkcolumn (sql3tableconstraint *tconstraint, size_t index) { 1385 | if (tconstraint->type != SQL3TABLECONSTRAINT_FOREIGNKEY) return NULL; 1386 | CHECK_IDX(index, tconstraint->foreignkey_num); 1387 | CHECK_STR(tconstraint->foreignkey_name[index]); 1388 | return &(tconstraint->foreignkey_name[index]); 1389 | } 1390 | 1391 | sql3foreignkey *sql3table_constraint_foreignkey_clause (sql3tableconstraint *tconstraint) { 1392 | if (tconstraint->type != SQL3TABLECONSTRAINT_FOREIGNKEY) return NULL; 1393 | return tconstraint->foreignkey_clause; 1394 | } 1395 | 1396 | // MARK: - Public Column Functions - 1397 | 1398 | sql3string *sql3column_name (sql3column *column) { 1399 | CHECK_STR(column->name); 1400 | return &column->name; 1401 | } 1402 | 1403 | sql3string *sql3column_type (sql3column *column) { 1404 | CHECK_STR(column->type); 1405 | return &column->type; 1406 | } 1407 | 1408 | sql3string *sql3column_length (sql3column *column) { 1409 | CHECK_STR(column->length); 1410 | return &column->length; 1411 | } 1412 | 1413 | sql3string *sql3column_constraint_name (sql3column *column) { 1414 | CHECK_STR(column->constraint_name); 1415 | return &column->constraint_name; 1416 | } 1417 | 1418 | sql3string *sql3column_comment (sql3column *column) { 1419 | CHECK_STR(column->comment); 1420 | return &column->comment; 1421 | } 1422 | 1423 | bool sql3column_is_primarykey (sql3column *column) { 1424 | return column->is_primarykey; 1425 | } 1426 | 1427 | bool sql3column_is_autoincrement (sql3column *column) { 1428 | return column->is_autoincrement; 1429 | } 1430 | 1431 | bool sql3column_is_notnull (sql3column *column) { 1432 | return column->is_notnull; 1433 | } 1434 | 1435 | bool sql3column_is_unique (sql3column *column) { 1436 | return column->is_unique; 1437 | } 1438 | 1439 | sql3order_clause sql3column_pk_order (sql3column *column) { 1440 | return column->pk_order; 1441 | } 1442 | 1443 | sql3conflict_clause sql3column_pk_conflictclause (sql3column *column) { 1444 | return column->pk_conflictclause; 1445 | } 1446 | 1447 | sql3conflict_clause sql3column_notnull_conflictclause (sql3column *column) { 1448 | return column->notnull_conflictclause; 1449 | } 1450 | 1451 | sql3conflict_clause sql3column_unique_conflictclause (sql3column *column) { 1452 | return column->unique_conflictclause; 1453 | } 1454 | 1455 | sql3string *sql3column_check_expr (sql3column *column) { 1456 | CHECK_STR(column->check_expr); 1457 | return &column->check_expr; 1458 | } 1459 | 1460 | sql3string *sql3column_default_expr (sql3column *column) { 1461 | CHECK_STR(column->default_expr); 1462 | return &column->default_expr; 1463 | } 1464 | 1465 | sql3string *sql3column_collate_name (sql3column *column) { 1466 | CHECK_STR(column->collate_name); 1467 | return &column->collate_name; 1468 | } 1469 | 1470 | sql3foreignkey *sql3column_foreignkey_clause (sql3column *column) { 1471 | return column->foreignkey_clause; 1472 | } 1473 | 1474 | // MARK: - Public Foreign Key Functions - 1475 | 1476 | sql3string *sql3foreignkey_table (sql3foreignkey *fk) { 1477 | CHECK_STR(fk->table); 1478 | return &fk->table; 1479 | } 1480 | 1481 | size_t sql3foreignkey_num_columns (sql3foreignkey *fk) { 1482 | return fk->num_columns; 1483 | } 1484 | 1485 | sql3string *sql3foreignkey_get_column (sql3foreignkey *fk, size_t index) { 1486 | if (index >= fk->num_columns) return NULL; 1487 | CHECK_STR(fk->column_name[index]); 1488 | 1489 | return &fk->column_name[index]; 1490 | } 1491 | 1492 | sql3fk_action sql3foreignkey_ondelete_action (sql3foreignkey *fk) { 1493 | return fk->on_delete; 1494 | } 1495 | 1496 | sql3fk_action sql3foreignkey_onupdate_action (sql3foreignkey *fk) { 1497 | return fk->on_update; 1498 | } 1499 | 1500 | sql3string *sql3foreignkey_match (sql3foreignkey *fk) { 1501 | CHECK_STR(fk->match); 1502 | return &fk->match; 1503 | } 1504 | 1505 | sql3fk_deftype sql3foreignkey_deferrable (sql3foreignkey *fk) { 1506 | return fk->deferrable; 1507 | } 1508 | 1509 | // MARK: - Public Index Column Functions - 1510 | 1511 | sql3string *sql3idxcolumn_name (sql3idxcolumn *idxcolumn) { 1512 | CHECK_STR(idxcolumn->name); 1513 | return &idxcolumn->name; 1514 | } 1515 | 1516 | sql3string *sql3idxcolumn_collate (sql3idxcolumn *idxcolumn) { 1517 | CHECK_STR(idxcolumn->collate_name); 1518 | return &idxcolumn->collate_name; 1519 | } 1520 | 1521 | sql3order_clause sql3idxcolumn_order (sql3idxcolumn *idxcolumn) { 1522 | return idxcolumn->order; 1523 | } 1524 | 1525 | // MARK: - Main Entrypoint - 1526 | 1527 | sql3table *sql3parse_table (const char *sql, size_t length, sql3error_code *error) { 1528 | // initial sanity check 1529 | if (sql == NULL) return NULL; 1530 | if (length == 0) length = strlen(sql); 1531 | if (error) *error = SQL3ERROR_NONE; 1532 | if (length == 0) return NULL; 1533 | 1534 | // allocate table 1535 | sql3table *table = SQL3MALLOC0(sizeof(sql3table)); 1536 | if (!table) goto error_memory; 1537 | 1538 | // setup state 1539 | sql3state state = {0}; 1540 | state.buffer = sql; 1541 | state.size = length; 1542 | state.table = table; 1543 | state.comment = &table->comment; 1544 | 1545 | // begin parsing 1546 | sql3error_code err = sql3parse(&state); 1547 | if (error) *error = err; 1548 | 1549 | // no error case, so return table 1550 | if (err == SQL3ERROR_NONE) return table; 1551 | 1552 | // an error occurred 1553 | SQL3FREE(table); 1554 | return NULL; 1555 | 1556 | error_memory: 1557 | if (table) SQL3FREE(table); 1558 | if (error) *error = SQL3ERROR_MEMORY; 1559 | return NULL; 1560 | } 1561 | 1562 | 1563 | -------------------------------------------------------------------------------- /sql3parse_table.h: -------------------------------------------------------------------------------- 1 | // 2 | // sql3parse_table.h 3 | // 4 | // Created by Marco Bambini on 14/02/16. 5 | // 6 | 7 | // Memory requirements on 64bit system: 8 | // number of columns WITHOUT a foreign key constraints => N1 9 | // number of columns WITH a foreign key constraints => N2 10 | // if table has a foreign key constraint then add 64 + (40 for each idx column) 11 | // Total memory = 144 + (N1 * 144) + (N2 * 208) + table_constraint_size 12 | 13 | #ifndef __SQL3PARSE_TABLE__ 14 | #define __SQL3PARSE_TABLE__ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | // Make sure we can call this stuff from C++ 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | // Redefine macros here if you want to use a custom allocator 29 | #define SQL3MALLOC(size) malloc(size) 30 | #define SQL3MALLOC0(size) calloc(1,size) 31 | #define SQL3FREE(ptr) free(ptr) 32 | #define SQL3REALLOC(ptr,size) realloc(ptr,size) 33 | 34 | // Opaque types that hold table, column and foreign key details 35 | typedef struct sql3table sql3table; 36 | typedef struct sql3column sql3column; 37 | typedef struct sql3idxcolumn sql3idxcolumn; 38 | typedef struct sql3foreignkey sql3foreignkey; 39 | typedef struct sql3tableconstraint sql3tableconstraint; 40 | typedef struct sql3string sql3string; 41 | typedef uint16_t sql3char; 42 | 43 | typedef enum { 44 | SQL3ERROR_NONE, 45 | SQL3ERROR_MEMORY, 46 | SQL3ERROR_SYNTAX, 47 | SQL3ERROR_UNSUPPORTEDSQL 48 | } sql3error_code; 49 | 50 | typedef enum { 51 | SQL3CONFLICT_NONE, 52 | SQL3CONFLICT_ROLLBACK, 53 | SQL3CONFLICT_ABORT, 54 | SQL3CONFLICT_FAIL, 55 | SQL3CONFLICT_IGNORE, 56 | SQL3CONFLICT_REPLACE 57 | } sql3conflict_clause; 58 | 59 | typedef enum { 60 | SQL3ORDER_NONE, 61 | SQL3ORDER_ASC, 62 | SQL3ORDER_DESC 63 | } sql3order_clause; 64 | 65 | typedef enum { 66 | SQL3FKACTION_NONE, 67 | SQL3FKACTION_SETNULL, 68 | SQL3FKACTION_SETDEFAULT, 69 | SQL3FKACTION_CASCADE, 70 | SQL3FKACTION_RESTRICT, 71 | SQL3FKACTION_NOACTION 72 | } sql3fk_action; 73 | 74 | typedef enum { 75 | SQL3DEFTYPE_NONE, 76 | SQL3DEFTYPE_DEFERRABLE, 77 | SQL3DEFTYPE_DEFERRABLE_INITIALLY_DEFERRED, 78 | SQL3DEFTYPE_DEFERRABLE_INITIALLY_IMMEDIATE, 79 | SQL3DEFTYPE_NOTDEFERRABLE, 80 | SQL3DEFTYPE_NOTDEFERRABLE_INITIALLY_DEFERRED, 81 | SQL3DEFTYPE_NOTDEFERRABLE_INITIALLY_IMMEDIATE 82 | } sql3fk_deftype; 83 | 84 | typedef enum { 85 | SQL3TABLECONSTRAINT_PRIMARYKEY, 86 | SQL3TABLECONSTRAINT_UNIQUE, 87 | SQL3TABLECONSTRAINT_CHECK, 88 | SQL3TABLECONSTRAINT_FOREIGNKEY 89 | } sql3constraint_type; 90 | 91 | typedef enum { 92 | SQL3CREATE_UNKNOWN, 93 | SQL3CREATE_TABLE, 94 | SQL3ALTER_RENAME_TABLE, 95 | SQL3ALTER_RENAME_COLUMN, 96 | SQL3ALTER_ADD_COLUMN, 97 | SQL3ALTER_DROP_COLUMN 98 | } sql3statement_type; 99 | 100 | // Main http://www.sqlite.org/lang_createtable.html 101 | sql3table *sql3parse_table (const char *sql, size_t length, sql3error_code *error); 102 | 103 | // Table Information 104 | sql3string *sql3table_schema (sql3table *table); 105 | sql3string *sql3table_name (sql3table *table); 106 | sql3string *sql3table_comment (sql3table *table); 107 | bool sql3table_is_temporary (sql3table *table); 108 | bool sql3table_is_ifnotexists (sql3table *table); 109 | bool sql3table_is_withoutrowid (sql3table *table); 110 | bool sql3table_is_strict (sql3table *table); 111 | size_t sql3table_num_columns (sql3table *table); 112 | sql3column *sql3table_get_column (sql3table *table, size_t index); 113 | size_t sql3table_num_constraints (sql3table *table); 114 | sql3tableconstraint *sql3table_get_constraint (sql3table *table, size_t index); 115 | void sql3table_free (sql3table *table); 116 | sql3statement_type sql3table_type (sql3table *table); 117 | sql3string *sql3table_current_name (sql3table *table); 118 | sql3string *sql3table_new_name (sql3table *table); 119 | 120 | // Table Constraint 121 | sql3string *sql3table_constraint_name (sql3tableconstraint *tconstraint); 122 | sql3constraint_type sql3table_constraint_type (sql3tableconstraint *tconstraint); 123 | size_t sql3table_constraint_num_idxcolumns (sql3tableconstraint *tconstraint); 124 | sql3idxcolumn *sql3table_constraint_get_idxcolumn (sql3tableconstraint *tconstraint, size_t index); 125 | sql3conflict_clause sql3table_constraint_conflict_clause (sql3tableconstraint *tconstraint); 126 | sql3string *sql3table_constraint_check_expr (sql3tableconstraint *tconstraint); 127 | size_t sql3table_constraint_num_fkcolumns (sql3tableconstraint *tconstraint); 128 | sql3string *sql3table_constraint_get_fkcolumn (sql3tableconstraint *tconstraint, size_t index); 129 | sql3foreignkey *sql3table_constraint_foreignkey_clause (sql3tableconstraint *tconstraint); 130 | 131 | // Column Constraint 132 | sql3string *sql3column_name (sql3column *column); 133 | sql3string *sql3column_type (sql3column *column); 134 | sql3string *sql3column_length (sql3column *column); 135 | sql3string *sql3column_constraint_name (sql3column *column); 136 | sql3string *sql3column_comment (sql3column *column); 137 | bool sql3column_is_primarykey (sql3column *column); 138 | bool sql3column_is_autoincrement (sql3column *column); 139 | bool sql3column_is_notnull (sql3column *column); 140 | bool sql3column_is_unique (sql3column *column); 141 | sql3order_clause sql3column_pk_order (sql3column *column); 142 | sql3conflict_clause sql3column_pk_conflictclause (sql3column *column); 143 | sql3conflict_clause sql3column_notnull_conflictclause (sql3column *column); 144 | sql3conflict_clause sql3column_unique_conflictclause (sql3column *column); 145 | sql3string *sql3column_check_expr (sql3column *column); 146 | sql3string *sql3column_default_expr (sql3column *column); 147 | sql3string *sql3column_collate_name (sql3column *column); 148 | sql3foreignkey *sql3column_foreignkey_clause (sql3column *column); 149 | 150 | // Foreign Key 151 | sql3string *sql3foreignkey_table (sql3foreignkey *fk); 152 | size_t sql3foreignkey_num_columns (sql3foreignkey *fk); 153 | sql3string *sql3foreignkey_get_column (sql3foreignkey *fk, size_t index); 154 | sql3fk_action sql3foreignkey_ondelete_action (sql3foreignkey *fk); 155 | sql3fk_action sql3foreignkey_onupdate_action (sql3foreignkey *fk); 156 | sql3string *sql3foreignkey_match (sql3foreignkey *fk); 157 | sql3fk_deftype sql3foreignkey_deferrable (sql3foreignkey *fk); 158 | 159 | // Indexed Column 160 | sql3string *sql3idxcolumn_name (sql3idxcolumn *idxcolumn); 161 | sql3string *sql3idxcolumn_collate (sql3idxcolumn *idxcolumn); 162 | sql3order_clause sql3idxcolumn_order (sql3idxcolumn *idxcolumn); 163 | 164 | // String Utils 165 | const char *sql3string_ptr (sql3string *s, size_t *length); 166 | const char *sql3string_cstring (sql3string *s); 167 | 168 | #ifdef __cplusplus 169 | } // end of the 'extern "C"' block 170 | #endif 171 | 172 | 173 | #endif 174 | --------------------------------------------------------------------------------