├── .gitignore ├── Diagnostics.pqm ├── DuckDB.pq ├── DuckDB.proj ├── DuckDB.query.pq ├── LICENSE ├── OdbcConstants.pqm ├── README.md ├── duckdb-logo-16.png ├── duckdb-logo-20.png ├── duckdb-logo-24.png ├── duckdb-logo-32.png ├── duckdb-logo-40.png ├── duckdb-logo-48.png ├── duckdb-logo-64.png ├── duckdb-logo-80.png ├── images ├── connect-duckdb.png ├── connect.png ├── find-connector.png ├── navigator.png ├── power-bi-example.png └── power_bi_options.png ├── resources.resx └── test └── test.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | bin 3 | *.db 4 | -------------------------------------------------------------------------------- /Diagnostics.pqm: -------------------------------------------------------------------------------- 1 | let 2 | Diagnostics.LogValue = (prefix, value, optional delayed) => 3 | Diagnostics.Trace( 4 | TraceLevel.Information, 5 | prefix & ": " & (try Diagnostics.ValueToText(value) otherwise ""), 6 | value, 7 | delayed 8 | ), 9 | Diagnostics.LogValue2 = (prefix, value, result, optional delayed) => 10 | Diagnostics.Trace(TraceLevel.Information, prefix & ": " & Diagnostics.ValueToText(value), result, delayed), 11 | Diagnostics.LogFailure = (text, function) => 12 | let 13 | result = try function() 14 | in 15 | if result[HasError] then 16 | Diagnostics.LogValue2(text, result[Error], () => error result[Error], true) 17 | else 18 | result[Value], 19 | Diagnostics.WrapFunctionResult = (innerFunction as function, outerFunction as function) as function => 20 | Function.From(Value.Type(innerFunction), (list) => outerFunction(() => Function.Invoke(innerFunction, list))), 21 | Diagnostics.WrapHandlers = (handlers as record) as record => 22 | Record.FromList( 23 | List.Transform( 24 | Record.FieldNames(handlers), 25 | (h) => 26 | Diagnostics.WrapFunctionResult(Record.Field(handlers, h), (fn) => Diagnostics.LogFailure(h, fn)) 27 | ), 28 | Record.FieldNames(handlers) 29 | ), 30 | Diagnostics.ValueToText = (value) => 31 | let 32 | List.TransformAndCombine = (list, transform, separator) => 33 | Text.Combine(List.Transform(list, transform), separator), 34 | Serialize.Binary = (x) => "#binary(" & Serialize(Binary.ToList(x)) & ") ", 35 | Serialize.Function = (x) => 36 | _serialize_function_param_type( 37 | Type.FunctionParameters(Value.Type(x)), Type.FunctionRequiredParameters(Value.Type(x)) 38 | ) 39 | & " as " 40 | & _serialize_function_return_type(Value.Type(x)) 41 | & " => (...) ", 42 | Serialize.List = (x) => "{" & List.TransformAndCombine(x, Serialize, ", ") & "} ", 43 | Serialize.Record = (x) => 44 | "[ " 45 | & List.TransformAndCombine( 46 | Record.FieldNames(x), 47 | (item) => Serialize.Identifier(item) & " = " & Serialize(Record.Field(x, item)), 48 | ", " 49 | ) 50 | & " ] ", 51 | Serialize.Table = (x) => 52 | "#table( type " & _serialize_table_type(Value.Type(x)) & ", " & Serialize(Table.ToRows(x)) & ") ", 53 | Serialize.Identifier = Expression.Identifier, 54 | Serialize.Type = (x) => "type " & _serialize_typename(x), 55 | _serialize_typename = (x, optional funtype as logical) => 56 | // Optional parameter: Is this being used as part of a function signature? 57 | let 58 | isFunctionType = (x as type) => 59 | try if Type.FunctionReturn(x) is type then true else false otherwise false, 60 | isTableType = (x as type) => 61 | try if Type.TableSchema(x) is table then true else false otherwise false, 62 | isRecordType = (x as type) => 63 | try if Type.ClosedRecord(x) is type then true else false otherwise false, 64 | isListType = (x as type) => try if Type.ListItem(x) is type then true else false otherwise false 65 | in 66 | if funtype = null and isTableType(x) then 67 | _serialize_table_type(x) 68 | else if funtype = null and isListType(x) then 69 | "{ " & @_serialize_typename(Type.ListItem(x)) & " }" 70 | else if funtype = null and isFunctionType(x) then 71 | "function " & _serialize_function_type(x) 72 | else if funtype = null and isRecordType(x) then 73 | _serialize_record_type(x) 74 | else if x = type any then 75 | "any" 76 | else 77 | let 78 | base = Type.NonNullable(x) 79 | in 80 | (if Type.IsNullable(x) then "nullable " else "") 81 | & ( 82 | if base = type anynonnull then 83 | "anynonnull" 84 | else if base = type binary then 85 | "binary" 86 | else if base = type date then 87 | "date" 88 | else if base = type datetime then 89 | "datetime" 90 | else if base = type datetimezone then 91 | "datetimezone" 92 | else if base = type duration then 93 | "duration" 94 | else if base = type logical then 95 | "logical" 96 | else if base = type none then 97 | "none" 98 | else if base = type null then 99 | "null" 100 | else if base = type number then 101 | "number" 102 | else if base = type text then 103 | "text" 104 | else if base = type time then 105 | "time" 106 | else if base = type type then 107 | "type" 108 | else 109 | // Abstract types 110 | if base = type function then 111 | "function" 112 | else if base = type table then 113 | "table" 114 | else if base = type record then 115 | "record" 116 | else if base = type list then 117 | "list" 118 | else 119 | "any /*Actually unknown type*/" 120 | ), 121 | _serialize_table_type = (x) => 122 | let 123 | schema = Type.TableSchema(x) 124 | in 125 | "table " 126 | & ( 127 | if Table.IsEmpty(schema) then 128 | "" 129 | else 130 | "[" 131 | & List.TransformAndCombine( 132 | Table.ToRecords(Table.Sort(schema, "Position")), 133 | each Serialize.Identifier(_[Name]) & " = " & _[Kind], 134 | ", " 135 | ) 136 | & "] " 137 | ), 138 | _serialize_record_type = (x) => 139 | let 140 | flds = Type.RecordFields(x) 141 | in 142 | if Record.FieldCount(flds) = 0 then 143 | "record" 144 | else 145 | "[" 146 | & List.TransformAndCombine( 147 | Record.FieldNames(flds), 148 | (item) => 149 | Serialize.Identifier(item) 150 | & "=" 151 | & _serialize_typename(Record.Field(flds, item)[Type]), 152 | ", " 153 | ) 154 | & (if Type.IsOpenRecord(x) then ", ..." else "") 155 | & "]", 156 | _serialize_function_type = (x) => 157 | _serialize_function_param_type(Type.FunctionParameters(x), Type.FunctionRequiredParameters(x)) 158 | & " as " 159 | & _serialize_function_return_type(x), 160 | _serialize_function_param_type = (t, n) => 161 | let 162 | funsig = Table.ToRecords( 163 | Table.TransformColumns( 164 | Table.AddIndexColumn(Record.ToTable(t), "isOptional", 1), {"isOptional", (x) => x > n} 165 | ) 166 | ) 167 | in 168 | "(" 169 | & List.TransformAndCombine( 170 | funsig, 171 | (item) => 172 | (if item[isOptional] then "optional " else "") 173 | & Serialize.Identifier(item[Name]) 174 | & " as " 175 | & _serialize_typename(item[Value], true), 176 | ", " 177 | ) 178 | & ")", 179 | _serialize_function_return_type = (x) => _serialize_typename(Type.FunctionReturn(x), true), 180 | Serialize = (x) as text => 181 | if x is binary then 182 | try Serialize.Binary(x) otherwise "null /*serialize failed*/" 183 | else if x is date then 184 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 185 | else if x is datetime then 186 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 187 | else if x is datetimezone then 188 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 189 | else if x is duration then 190 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 191 | else if x is function then 192 | try Serialize.Function(x) otherwise "null /*serialize failed*/" 193 | else if x is list then 194 | try Serialize.List(x) otherwise "null /*serialize failed*/" 195 | else if x is logical then 196 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 197 | else if x is null then 198 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 199 | else if x is number then 200 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 201 | else if x is record then 202 | try Serialize.Record(x) otherwise "null /*serialize failed*/" 203 | else if x is table then 204 | try Serialize.Table(x) otherwise "null /*serialize failed*/" 205 | else if x is text then 206 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 207 | else if x is time then 208 | try Expression.Constant(x) otherwise "null /*serialize failed*/" 209 | else if x is type then 210 | try Serialize.Type(x) otherwise "null /*serialize failed*/" 211 | else 212 | "[#_unable_to_serialize_#]" 213 | in 214 | try Serialize(value) otherwise "" 215 | in 216 | [ 217 | LogValue = Diagnostics.LogValue, 218 | LogValue2 = Diagnostics.LogValue2, 219 | LogFailure = Diagnostics.LogFailure, 220 | WrapFunctionResult = Diagnostics.WrapFunctionResult, 221 | WrapHandlers = Diagnostics.WrapHandlers, 222 | ValueToText = Diagnostics.ValueToText 223 | ] 224 | -------------------------------------------------------------------------------- /DuckDB.pq: -------------------------------------------------------------------------------- 1 | // This file contains your Data Connector logic 2 | [Version = "0.1.7"] 3 | section DuckDB; 4 | 5 | // When set to true, additional trace information will be written out to the User log. 6 | // This should be set to false before release. Tracing is done through a call to 7 | // Diagnostics.LogValue(). When EnableTraceOutput is set to false, the call becomes a 8 | // no-op and simply returns the original value. 9 | EnableTraceOutput = true; 10 | 11 | // The name of the ODBC driver 12 | Config_DriverName = "DuckDB Driver"; 13 | 14 | // Set to null to determine the value from the driver. 15 | Config_SqlConformance = null; 16 | 17 | // DuckDB only supports OFFSET when LIMIT is also specified, 18 | // so we will use LimitClauseKind.Limit. 19 | Config_LimitClauseKind = LimitClauseKind.Limit; 20 | 21 | // DuckDB does not support standard username/password 22 | // handling through the UID and PWD connection string parameters. 23 | Config_DefaultUsernamePasswordHandling = false; 24 | 25 | // If the driver supports parameter bindings, then set this to true. 26 | Config_UseParameterBindings = null; 27 | 28 | // Override this setting to force the character escape value. 29 | Config_StringLiterateEscapeCharacters = null; 30 | 31 | // Override this if the driver expects the use of CAST instead of CONVERT. 32 | Config_UseCastInsteadOfConvert = null; 33 | 34 | // Set this to true to enable Direct Query in addition to Import mode. 35 | Config_EnableDirectQuery = true; 36 | 37 | // Default custom user agent 38 | DefaultCustomUserAgent = "powerbi/v0.0(DuckDB)"; 39 | 40 | [DataSource.Kind="DuckDB", Publish="DuckDB.Publish"] 41 | shared DuckDB.Contents = ( 42 | database as text, 43 | motherduck_token as text, 44 | optional read_only as logical, 45 | optional saas_mode as logical, 46 | optional attach_mode as text, 47 | optional options as record 48 | ) => 49 | let 50 | Config_DriverName = Config_DriverName, 51 | // Read-only 52 | AccessMode = if (read_only = true) 53 | then 54 | "read_only" 55 | else if (read_only = false) 56 | then 57 | "read_write" 58 | else if (Record.HasFields(options, "access_mode") = true) 59 | then 60 | options[access_mode] 61 | else 62 | "automatic", 63 | // Custom user agent 64 | CustomUserAgent = if (Record.HasFields(options, "custom_user_agent") = true) 65 | then 66 | Text.Combine({DefaultCustomUserAgent, options[custom_user_agent]}, " ") 67 | else 68 | DefaultCustomUserAgent, 69 | Credential = Extension.CurrentCredential(), 70 | token = try Credential[Password] otherwise motherduck_token, 71 | // The ODBC connection string 72 | ConnectionString = options & [ 73 | Driver = Config_DriverName, 74 | Database = GetFullPath(database, token, saas_mode, attach_mode), 75 | access_mode = AccessMode, 76 | custom_user_agent = CustomUserAgent 77 | ], 78 | // 79 | // Configuration options for the call to Odbc.DataSource 80 | // 81 | defaultConfig = Diagnostics.LogValue("BuildOdbcConfig", BuildOdbcConfig()), 82 | SqlCapabilities = Diagnostics.LogValue( 83 | "SqlCapabilities_Options", defaultConfig[SqlCapabilities] & [ 84 | SupportsTop = false, 85 | SupportsDerivedTable = true, 86 | Sql92Conformance = 8 /* SQL_SC_SQL92_FULL */, 87 | GroupByCapabilities = 4 /* SQL_GB_NO_RELATION */, 88 | FractionalSecondsScale = 3, 89 | // Enable Native query 90 | Sql92Translation = "PassThrough" 91 | ] 92 | ), 93 | // Please refer to the ODBC specification for SQLGetInfo properties and values. 94 | // https://github.com/Microsoft/ODBC-Specification/blob/master/Windows/inc/sqlext.h 95 | SQLGetInfo = Diagnostics.LogValue( 96 | "SQLGetInfo_Options", 97 | defaultConfig[SQLGetInfo] 98 | & [ 99 | // Place custom overrides here 100 | // The values below are required for the SQL Native Client ODBC driver, but might 101 | // not be required for your data source. 102 | SQL_SQL92_PREDICATES = ODBC[SQL_SP][All], 103 | SQL_AGGREGATE_FUNCTIONS = ODBC[SQL_AF][All] 104 | ] 105 | ), 106 | // SQLGetTypeInfo can be specified in two ways: 107 | // 1. A #table() value that returns the same type information as an ODBC 108 | // call to SQLGetTypeInfo. 109 | // 2. A function that accepts a table argument, and returns a table. The 110 | // argument will contain the original results of the ODBC call to SQLGetTypeInfo. 111 | // Your function implementation can modify/add to this table. 112 | // 113 | // For details of the format of the types table parameter and expected return value, 114 | // please see: https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgettypeinfo-function 115 | // 116 | // The sample implementation provided here will simply output the original table 117 | // to the user trace log, without any modification. 118 | SQLGetTypeInfo = (types) => 119 | if (EnableTraceOutput <> true) then 120 | types 121 | else 122 | let 123 | // Outputting the entire table might be too large, and result in the value being truncated. 124 | // We can output a row at a time instead with Table.TransformRows() 125 | rows = Table.TransformRows(types, each Diagnostics.LogValue("SQLGetTypeInfo " & _[TYPE_NAME], _)), 126 | toTable = Table.FromRecords(rows) 127 | in 128 | Value.ReplaceType(toTable, Value.Type(types)), 129 | Options = [ 130 | // A logical (true/false) that sets whether to view the tables grouped by their schema names 131 | HierarchicalNavigation = false, 132 | // Allows upconversion of numeric types 133 | SoftNumbers = true, 134 | // Allow upconversion / resizing of numeric and string types 135 | TolerateConcatOverflow = true, 136 | // Enables connection pooling via the system ODBC manager 137 | ClientConnectionPooling = false, 138 | // Other configuration options 139 | SqlCapabilities = SqlCapabilities, 140 | SQLGetInfo = SQLGetInfo, 141 | SQLGetTypeInfo = SQLGetTypeInfo 142 | ], 143 | // Create and return the ODBC data source 144 | OdbcDatasource = Odbc.DataSource( 145 | ConnectionString, 146 | Options 147 | ) 148 | in 149 | OdbcDatasource; 150 | 151 | // Data Source Kind description 152 | DuckDB = [ 153 | // Set the TestConnection handler to enable gateway support. 154 | // The TestConnection handler will invoke your data source function to 155 | // validate the credentials the user has provider. Ideally, this is not 156 | // an expensive operation to perform. By default, the dataSourcePath value 157 | // will be a json string containing the required parameters of your data 158 | // source function. These should be parsed and parsed as individual parameters 159 | // to the specified data source function. 160 | TestConnection = (dataSourcePath) => 161 | let 162 | params = Json.Document(dataSourcePath), 163 | database = params[database], 164 | motherduck_token = params[motherduck_token], 165 | // optional parameters are not stored in dataSourcePath so we will set default values here 166 | read_only = true, 167 | saas_mode = false, 168 | attach_mode = "single", 169 | options = try params[options] otherwise [custom_user_agent="On-premises Data Gateway Test Connection"] 170 | in 171 | {"DuckDB.Contents", database, motherduck_token, read_only, saas_mode, attach_mode, options}, 172 | // Set supported types of authentication 173 | Authentication = [ 174 | Anonymous = [], 175 | UsernamePassword = [] 176 | ], 177 | Label = Extension.LoadString("DataSourceLabel") 178 | ]; 179 | 180 | // Data Source UI publishing description 181 | DuckDB.Publish = [ 182 | Beta = true, 183 | Category = "Other", 184 | ButtonText = { Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp") }, 185 | LearnMoreUrl = "https://powerbi.microsoft.com/", 186 | SupportsDirectQuery = Config_EnableDirectQuery, 187 | SourceImage = DuckDB.Icons, 188 | SourceTypeImage = DuckDB.Icons 189 | ]; 190 | 191 | DuckDB.Icons = [ 192 | Icon16 = { Extension.Contents("duckdb-logo-16.png"), Extension.Contents("duckdb-logo-20.png"), Extension.Contents("duckdb-logo-24.png"), Extension.Contents("duckdb-logo-32.png") }, 193 | Icon32 = { Extension.Contents("duckdb-logo-32.png"), Extension.Contents("duckdb-logo-40.png"), Extension.Contents("duckdb-logo-48.png"), Extension.Contents("duckdb-logo-64.png") } 194 | ]; 195 | 196 | // Get the full DuckDB path to pass to the ODBC driver 197 | GetFullPath = (database as text, optional motherduck_token as text, optional saas_mode as logical, optional attach_mode as text) as text => 198 | let 199 | attach_mode = if (attach_mode = null or attach_mode = "") 200 | then "single" 201 | else attach_mode, 202 | path = if IsMotherDuckDatabase(database) and saas_mode = true 203 | then database & "?motherduck_token=" & motherduck_token & "&saas_mode=true&attach_mode=" & attach_mode 204 | else if IsMotherDuckDatabase(database) 205 | then database & "?motherduck_token=" & motherduck_token & "&attach_mode=" & attach_mode 206 | else database 207 | in 208 | if IsMotherDuckDatabase(database) and (motherduck_token = null or motherduck_token = "") 209 | then 210 | error Error.Record("Expression.Error", 211 | "Cannot connect to MotherDuck database: 212 | Please enter your connection token. 213 | Go to http://app.motherduck.com/ and copy 214 | it from your User Settings.") 215 | else 216 | path; 217 | 218 | // Check if the database is a MotherDuck database 219 | IsMotherDuckDatabase = (database as text) as logical => 220 | let 221 | result = Text.StartsWith(database, "md:") 222 | in 223 | result; 224 | 225 | // build settings based on configuration variables 226 | BuildOdbcConfig = () as record => 227 | let 228 | Merge = (previous as record, optional caps as record, optional funcs as record, optional getInfo as record) as record => 229 | let 230 | newCaps = if (caps <> null) then previous[SqlCapabilities] & caps else previous[SqlCapabilities], 231 | newFuncs = if (funcs <> null) then previous[SQLGetFunctions] & funcs else previous[SQLGetFunctions], 232 | newGetInfo = if (getInfo <> null) then previous[SQLGetInfo] & getInfo else previous[SQLGetInfo] 233 | in 234 | [SqlCapabilities = newCaps, SQLGetFunctions = newFuncs, SQLGetInfo = newGetInfo], 235 | defaultConfig = [ 236 | SqlCapabilities = [], 237 | SQLGetFunctions = [], 238 | SQLGetInfo = [] 239 | ], 240 | withParams = 241 | if (Config_UseParameterBindings = false) then 242 | let 243 | caps = [ 244 | SupportsNumericLiterals = true, 245 | SupportsStringLiterals = true, 246 | SupportsOdbcDateLiterals = true, 247 | SupportsOdbcTimeLiterals = true, 248 | SupportsOdbcTimestampLiterals = true 249 | ], 250 | funcs = [ 251 | SQL_API_SQLBINDPARAMETER = false 252 | ] 253 | in 254 | Merge(defaultConfig, caps, funcs) 255 | else 256 | defaultConfig, 257 | withEscape = 258 | if (Config_StringLiterateEscapeCharacters <> null) then 259 | let 260 | caps = [ 261 | StringLiteralEscapeCharacters = Config_StringLiterateEscapeCharacters 262 | ] 263 | in 264 | Merge(withParams, caps) 265 | else 266 | withParams, 267 | withLimitClauseKind = let caps = [ 268 | LimitClauseKind = Config_LimitClauseKind 269 | ] in Merge(withEscape, caps), 270 | withCastOrConvert = 271 | if (Config_UseCastInsteadOfConvert <> null) then 272 | let 273 | value = 274 | if (Config_UseCastInsteadOfConvert = true) then 275 | ODBC[SQL_FN_CVT][SQL_FN_CVT_CAST] 276 | else 277 | ODBC[SQL_FN_CVT][SQL_FN_CVT_CONVERT], 278 | getInfo = [ 279 | SQL_CONVERT_FUNCTIONS = value 280 | ] 281 | in 282 | Merge(withLimitClauseKind, null, null, getInfo) 283 | else 284 | withLimitClauseKind, 285 | withSqlConformance = 286 | if (Config_SqlConformance <> null) then 287 | let 288 | getInfo = [ 289 | SQL_SQL_CONFORMANCE = Config_SqlConformance 290 | ] 291 | in 292 | Merge(withCastOrConvert, null, null, getInfo) 293 | else 294 | withCastOrConvert 295 | in 296 | withSqlConformance; 297 | 298 | ValidateOptions = (options as nullable record, validOptionsMap as table) as record => 299 | let 300 | ValidKeys = Table.Column(validOptionsMap, "Name"), 301 | InvalidKeys = List.Difference(Record.FieldNames(options), ValidKeys), 302 | InvalidKeysText = 303 | if List.IsEmpty(InvalidKeys) then 304 | null 305 | else 306 | Text.Format( 307 | "'#{0}' are not valid options. Valid options are: '#{1}'", 308 | {Text.Combine(InvalidKeys, ", "), Text.Combine(ValidKeys, ", ")} 309 | ), 310 | ValidateValue = (name, optionType, description, default, validate, value) => 311 | if 312 | (value is null and (Type.IsNullable(optionType) or default <> null)) 313 | or (Type.Is(Value.Type(value), optionType) and validate(value)) 314 | then 315 | null 316 | else 317 | Text.Format( 318 | "This function does not support the option '#{0}' with value '#{1}'. Valid value is #{2}.", 319 | {name, value, description} 320 | ), 321 | InvalidValues = List.RemoveNulls( 322 | Table.TransformRows( 323 | validOptionsMap, 324 | each 325 | ValidateValue( 326 | [Name], 327 | [Type], 328 | [Description], 329 | [Default], 330 | [Validate], 331 | Record.FieldOrDefault(options, [Name], [Default]) 332 | ) 333 | ) 334 | ), 335 | DefaultOptions = Record.FromTable( 336 | Table.RenameColumns(Table.SelectColumns(validOptionsMap, {"Name", "Default"}), {"Default", "Value"}) 337 | ), 338 | NullNotAllowedFields = List.RemoveNulls( 339 | Table.TransformRows( 340 | validOptionsMap, 341 | each 342 | if not Type.IsNullable([Type]) and null = Record.FieldOrDefault(options, [Name], [Default]) then 343 | [Name] 344 | else 345 | null 346 | ) 347 | ), 348 | NormalizedOptions = DefaultOptions & Record.RemoveFields(options, NullNotAllowedFields, MissingField.Ignore) 349 | in 350 | if null = options then 351 | DefaultOptions 352 | else if not List.IsEmpty(InvalidKeys) then 353 | error Error.Record("Expression.Error", InvalidKeysText) 354 | else if not List.IsEmpty(InvalidValues) then 355 | error Error.Record("Expression.Error", Text.Combine(InvalidValues, ", ")) 356 | else 357 | NormalizedOptions; 358 | 359 | // 360 | // Load common library functions 361 | // 362 | Extension.LoadFunction = (name as text) => 363 | let 364 | binary = Extension.Contents(name), asText = Text.FromBinary(binary) 365 | in 366 | Expression.Evaluate(asText, #shared); 367 | 368 | // Diagnostics module contains multiple functions. We can take the ones we need. 369 | Diagnostics = Extension.LoadFunction("Diagnostics.pqm"); 370 | 371 | Diagnostics.LogValue = if (EnableTraceOutput) then Diagnostics[LogValue] else (prefix, value) => value; 372 | 373 | // OdbcConstants contains numeric constants from the ODBC header files, and a 374 | // helper function to create bitfield values. 375 | ODBC = Extension.LoadFunction("OdbcConstants.pqm"); 376 | -------------------------------------------------------------------------------- /DuckDB.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\bin\AnyCPU\Debug\ 5 | $(MSBuildProjectDirectory)\obj\ 6 | $(IntermediateOutputPath)MEZ\ 7 | $(OutputPath)$(MsBuildProjectName).mez 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /DuckDB.query.pq: -------------------------------------------------------------------------------- 1 | // Use this file to write queries to test your data connector 2 | let 3 | options = [custom_user_agent="vscode"], 4 | Database = "~\test.db", 5 | // Uncomment line below to connect to MotherDuck 6 | // Database = "md:", 7 | // To get your MotherDuck token, go to https://app.motherduck.com/token-request?appName=PowerQuerySDK 8 | MotherDuckToken = "", 9 | // ReadOnly mode (true or false) 10 | ReadOnly=true, 11 | // (Optional for MD) SaaS mode (true or false) 12 | SaaSMode=false, 13 | // (Optional for MD) AttachMode ("workspace" or "single") 14 | AttachMode="single", 15 | // Connect to data source 16 | OdbcDatasource = DuckDB.Contents( 17 | Database, 18 | MotherDuckToken, 19 | ReadOnly, 20 | SaaSMode, 21 | AttachMode, 22 | options 23 | ) 24 | in 25 | OdbcDatasource{[Item="test"]}[Data] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MotherDuck 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 | -------------------------------------------------------------------------------- /OdbcConstants.pqm: -------------------------------------------------------------------------------- 1 | // values from https://github.com/Microsoft/ODBC-Specification/blob/master/Windows/inc/sqlext.h 2 | [ 3 | Flags = (flags as list) => 4 | if (List.IsEmpty(flags)) then 5 | 0 6 | else 7 | let 8 | Loop = List.Generate( 9 | () => [i = 0, Combined = flags{0}], 10 | each [i] < List.Count(flags), 11 | each [Combined = Number.BitwiseOr([Combined], flags{i}), i = [i] + 1], 12 | each [Combined] 13 | ), 14 | Result = List.Last(Loop) 15 | in 16 | Result, 17 | SQL_HANDLE = [ 18 | ENV = 1, 19 | DBC = 2, 20 | STMT = 3, 21 | DESC = 4 22 | ], 23 | RetCode = [ 24 | SUCCESS = 0, 25 | SUCCESS_WITH_INFO = 1, 26 | ERROR = -1, 27 | INVALID_HANDLE = -2, 28 | NO_DATA = 100 29 | ], 30 | SQL_CONVERT = [ 31 | BIGINT = 53, 32 | BINARY = 54, 33 | BIT = 55, 34 | CHAR = 56, 35 | DATE = 57, 36 | DECIMAL = 58, 37 | DOUBLE = 59, 38 | FLOAT = 60, 39 | INTEGER = 61, 40 | LONGVARCHAR = 62, 41 | NUMERIC = 63, 42 | REAL = 64, 43 | SMALLINT = 65, 44 | TIME = 66, 45 | TIMESTAMP = 67, 46 | TINYINT = 68, 47 | VARBINARY = 69, 48 | VARCHAR = 70, 49 | LONGVARBINARY = 71 50 | ], 51 | SQL_ROW = [ 52 | PROCEED = 0, 53 | IGNORE = 1, 54 | SUCCESS = 0, 55 | DELETED = 1, 56 | UPDATED = 2, 57 | NOROW = 3, 58 | ADDED = 4, 59 | ERROR = 5, 60 | SUCCESS_WITH_INFO = 6 61 | ], 62 | SQL_CVT = [ 63 | // None = 0, 64 | CHAR = 0x00000001, 65 | NUMERIC = 0x00000002, 66 | DECIMAL = 0x00000004, 67 | INTEGER = 0x00000008, 68 | SMALLINT = 0x00000010, 69 | FLOAT = 0x00000020, 70 | REAL = 0x00000040, 71 | DOUBLE = 0x00000080, 72 | VARCHAR = 0x00000100, 73 | LONGVARCHAR = 0x00000200, 74 | BINARY = 0x00000400, 75 | VARBINARY = 0x00000800, 76 | BIT = 0x00001000, 77 | TINYINT = 0x00002000, 78 | BIGINT = 0x00004000, 79 | DATE = 0x00008000, 80 | TIME = 0x00010000, 81 | TIMESTAMP = 0x00020000, 82 | LONGVARBINARY = 0x00040000, 83 | INTERVAL_YEAR_MONTH = 0x00080000, 84 | INTERVAL_DAY_TIME = 0x00100000, 85 | WCHAR = 0x00200000, 86 | WLONGVARCHAR = 0x00400000, 87 | WVARCHAR = 0x00800000, 88 | GUID = 0x01000000 89 | ], 90 | STMT = [ 91 | CLOSE = 0, 92 | DROP = 1, 93 | UNBIND = 2, 94 | RESET_PARAMS = 3 95 | ], 96 | SQL_MAX = [ 97 | NUMERIC_LEN = 16 98 | ], 99 | SQL_IS = [ 100 | POINTER = -4, 101 | INTEGER = -6, 102 | UINTEGER = -5, 103 | SMALLINT = -8 104 | ], 105 | // SQL Server specific defines 106 | // 107 | // from Odbcss.h 108 | SQL_HC = [ 109 | OFF = 0 /* FOR BROWSE columns are hidden */, 110 | ON = 1 /* FOR BROWSE columns are exposed */ 111 | ], 112 | // from Odbcss.h 113 | SQL_NB = [ 114 | // NO_BROWSETABLE is off 115 | OFF = 0, 116 | // NO_BROWSETABLE is on 117 | ON = 1 118 | ], 119 | // SQLColAttributes driver specific defines. 120 | // SQLSet/GetDescField driver specific defines. 121 | // Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server driver usage. 122 | // 123 | // from Odbcss.h 124 | SQL_CA_SS = [ 125 | // SQL_CA_SS_BASE 126 | BASE = 1200, 127 | // Column is hidden (FOR BROWSE) 128 | COLUMN_HIDDEN = 1200 + 11, 129 | // Column is key column (FOR BROWSE) 130 | COLUMN_KEY = 1200 + 12, 131 | VARIANT_TYPE = 1200 + 15, 132 | VARIANT_SQL_TYPE = 1200 + 16, 133 | VARIANT_SERVER_TYPE = 1200 + 17 134 | ], 135 | // from Odbcss.h 136 | SQL_SOPT_SS = [ 137 | // SQL_SOPT_SS_BASE 138 | BASE = 1225, 139 | // Expose FOR BROWSE hidden columns 140 | HIDDEN_COLUMNS = 1225 + 2, 141 | // Set NOBROWSETABLE option 142 | NOBROWSETABLE = 1225 + 3 143 | ], 144 | SQL_COMMIT = 0, 145 | // Commit 146 | SQL_ROLLBACK = 1, 147 | // Abort 148 | // static public readonly IntPtr SQL_AUTOCOMMIT_OFF = IntPtr.Zero; 149 | // static public readonly IntPtr SQL_AUTOCOMMIT_ON = new IntPtr(1); 150 | SQL_TRANSACTION = [ 151 | READ_UNCOMMITTED = 0x00000001, 152 | READ_COMMITTED = 0x00000002, 153 | REPEATABLE_READ = 0x00000004, 154 | SERIALIZABLE = 0x00000008, 155 | SNAPSHOT = 0x00000020 156 | // VSDD 414121: SQL_TXN_SS_SNAPSHOT == 0x20 (sqlncli.h) 157 | ], 158 | SQL_PARAM = [ 159 | // SQL_PARAM_TYPE_UNKNOWN 160 | TYPE_UNKNOWN = 0, 161 | // SQL_PARAM_INPUT 162 | INPUT = 1, 163 | // SQL_PARAM_INPUT_OUTPUT 164 | INPUT_OUTPUT = 2, 165 | // SQL_RESULT_COL 166 | RESULT_COL = 3, 167 | // SQL_PARAM_OUTPUT 168 | OUTPUT = 4, 169 | // SQL_RETURN_VALUE 170 | RETURN_VALUE = 5 171 | ], 172 | SQL_DESC = [ 173 | // from sql.h (ODBCVER >= 3.0) 174 | // 175 | COUNT = 1001, 176 | TYPE = 1002, 177 | LENGTH = 1003, 178 | OCTET_LENGTH_PTR = 1004, 179 | PRECISION = 1005, 180 | SCALE = 1006, 181 | DATETIME_INTERVAL_CODE = 1007, 182 | NULLABLE = 1008, 183 | INDICATOR_PTR = 1009, 184 | DATA_PTR = 1010, 185 | NAME = 1011, 186 | UNNAMED = 1012, 187 | OCTET_LENGTH = 1013, 188 | ALLOC_TYPE = 1099, 189 | // from sqlext.h (ODBCVER >= 3.0) 190 | // 191 | CONCISE_TYPE = SQL_COLUMN[TYPE], 192 | DISPLAY_SIZE = SQL_COLUMN[DISPLAY_SIZE], 193 | UNSIGNED = SQL_COLUMN[UNSIGNED], 194 | UPDATABLE = SQL_COLUMN[UPDATABLE], 195 | AUTO_UNIQUE_VALUE = SQL_COLUMN[AUTO_INCREMENT], 196 | TYPE_NAME = SQL_COLUMN[TYPE_NAME], 197 | TABLE_NAME = SQL_COLUMN[TABLE_NAME], 198 | SCHEMA_NAME = SQL_COLUMN[OWNER_NAME], 199 | CATALOG_NAME = SQL_COLUMN[QUALIFIER_NAME], 200 | BASE_COLUMN_NAME = 22, 201 | BASE_TABLE_NAME = 23, 202 | NUM_PREC_RADIX = 32 203 | ], 204 | // ODBC version 2.0 style attributes 205 | // All IdentifierValues are ODBC 1.0 unless marked differently 206 | // 207 | SQL_COLUMN = [ 208 | COUNT = 0, 209 | NAME = 1, 210 | TYPE = 2, 211 | LENGTH = 3, 212 | PRECISION = 4, 213 | SCALE = 5, 214 | DISPLAY_SIZE = 6, 215 | NULLABLE = 7, 216 | UNSIGNED = 8, 217 | MONEY = 9, 218 | UPDATABLE = 10, 219 | AUTO_INCREMENT = 11, 220 | CASE_SENSITIVE = 12, 221 | SEARCHABLE = 13, 222 | TYPE_NAME = 14, 223 | // (ODBC 2.0) 224 | TABLE_NAME = 15, 225 | // (ODBC 2.0) 226 | OWNER_NAME = 16, 227 | // (ODBC 2.0) 228 | QUALIFIER_NAME = 17, 229 | LABEL = 18 230 | ], 231 | // values from sqlext.h 232 | SQL_SQL92_RELATIONAL_JOIN_OPERATORS = [ 233 | // SQL_SRJO_CORRESPONDING_CLAUSE 234 | CORRESPONDING_CLAUSE = 0x00000001, 235 | // SQL_SRJO_CROSS_JOIN 236 | CROSS_JOIN = 0x00000002, 237 | // SQL_SRJO_EXCEPT_JOIN 238 | EXCEPT_JOIN = 0x00000004, 239 | // SQL_SRJO_FULL_OUTER_JOIN 240 | FULL_OUTER_JOIN = 0x00000008, 241 | // SQL_SRJO_INNER_JOIN 242 | INNER_JOIN = 0x00000010, 243 | // SQL_SRJO_INTERSECT_JOIN 244 | INTERSECT_JOIN = 0x00000020, 245 | // SQL_SRJO_LEFT_OUTER_JOIN 246 | LEFT_OUTER_JOIN = 0x00000040, 247 | // SQL_SRJO_NATURAL_JOIN 248 | NATURAL_JOIN = 0x00000080, 249 | // SQL_SRJO_RIGHT_OUTER_JOIN 250 | RIGHT_OUTER_JOIN = 0x00000100, 251 | // SQL_SRJO_UNION_JOIN 252 | UNION_JOIN = 0x00000200 253 | ], 254 | // values from sqlext.h 255 | SQL_QU = [ 256 | SQL_QU_DML_STATEMENTS = 0x00000001, 257 | SQL_QU_PROCEDURE_INVOCATION = 0x00000002, 258 | SQL_QU_TABLE_DEFINITION = 0x00000004, 259 | SQL_QU_INDEX_DEFINITION = 0x00000008, 260 | SQL_QU_PRIVILEGE_DEFINITION = 0x00000010 261 | ], 262 | // values from sql.h 263 | SQL_OJ_CAPABILITIES = [ 264 | // SQL_OJ_LEFT 265 | LEFT = 0x00000001, 266 | // SQL_OJ_RIGHT 267 | RIGHT = 0x00000002, 268 | // SQL_OJ_FULL 269 | FULL = 0x00000004, 270 | // SQL_OJ_NESTED 271 | NESTED = 0x00000008, 272 | // SQL_OJ_NOT_ORDERED 273 | NOT_ORDERED = 0x00000010, 274 | // SQL_OJ_INNER 275 | INNER = 0x00000020, 276 | // SQL_OJ_ALLCOMPARISION+OPS 277 | ALL_COMPARISON_OPS = 0x00000040 278 | ], 279 | SQL_UPDATABLE = [ 280 | // SQL_ATTR_READ_ONLY 281 | READONLY = 0, 282 | // SQL_ATTR_WRITE 283 | WRITE = 1, 284 | // SQL_ATTR_READWRITE_UNKNOWN 285 | READWRITE_UNKNOWN = 2 286 | ], 287 | SQL_IDENTIFIER_CASE = [ 288 | // SQL_IC_UPPER 289 | UPPER = 1, 290 | // SQL_IC_LOWER 291 | LOWER = 2, 292 | // SQL_IC_SENSITIVE 293 | SENSITIVE = 3, 294 | // SQL_IC_MIXED 295 | MIXED = 4 296 | ], 297 | // Uniqueness parameter in the SQLStatistics function 298 | SQL_INDEX = [ 299 | UNIQUE = 0, 300 | ALL = 1 301 | ], 302 | // Reserved parameter in the SQLStatistics function 303 | SQL_STATISTICS_RESERVED = [ 304 | // SQL_QUICK 305 | QUICK = 0, 306 | // SQL_ENSURE 307 | ENSURE = 1 308 | ], 309 | // Identifier type parameter in the SQLSpecialColumns function 310 | SQL_SPECIALCOLS = [ 311 | // SQL_BEST_ROWID 312 | BEST_ROWID = 1, 313 | // SQL_ROWVER 314 | ROWVER = 2 315 | ], 316 | // Scope parameter in the SQLSpecialColumns function 317 | SQL_SCOPE = [ 318 | // SQL_SCOPE_CURROW 319 | CURROW = 0, 320 | // SQL_SCOPE_TRANSACTION 321 | TRANSACTION = 1, 322 | // SQL_SCOPE_SESSION 323 | SESSION = 2 324 | ], 325 | SQL_NULLABILITY = [ 326 | // SQL_NO_NULLS 327 | NO_NULLS = 0, 328 | // SQL_NULLABLE 329 | NULLABLE = 1, 330 | // SQL_NULLABLE_UNKNOWN 331 | UNKNOWN = 2 332 | ], 333 | SQL_SEARCHABLE = [ 334 | // SQL_UNSEARCHABLE 335 | UNSEARCHABLE = 0, 336 | // SQL_LIKE_ONLY 337 | LIKE_ONLY = 1, 338 | // SQL_ALL_EXCEPT_LIKE 339 | ALL_EXCEPT_LIKE = 2, 340 | // SQL_SEARCHABLE 341 | SEARCHABLE = 3 342 | ], 343 | SQL_UNNAMED = [ 344 | // SQL_NAMED 345 | NAMED = 0, 346 | // SQL_UNNAMED 347 | UNNAMED = 1 348 | ], 349 | // todo:move 350 | // internal constants 351 | // not odbc specific 352 | // 353 | HANDLER = [ 354 | IGNORE = 0x00000000, 355 | THROW = 0x00000001 356 | ], 357 | // values for SQLStatistics TYPE column 358 | SQL_STATISTICSTYPE = [ 359 | // TABLE Statistics 360 | TABLE_STAT = 0, 361 | // CLUSTERED index statistics 362 | INDEX_CLUSTERED = 1, 363 | // HASHED index statistics 364 | INDEX_HASHED = 2, 365 | // OTHER index statistics 366 | INDEX_OTHER = 3 367 | ], 368 | // values for SQLProcedures PROCEDURE_TYPE column 369 | SQL_PROCEDURETYPE = [ 370 | // procedure is of unknow type 371 | UNKNOWN = 0, 372 | // procedure is a procedure 373 | PROCEDURE = 1, 374 | // procedure is a function 375 | FUNCTION = 2 376 | ], 377 | // private constants 378 | // to define data types (see below) 379 | // 380 | // SQL_SIGNED_OFFSET 381 | SIGNED_OFFSET = -20, 382 | // SQL_UNSIGNED_OFFSET 383 | UNSIGNED_OFFSET = -22, 384 | // C Data Types 385 | SQL_C = [ 386 | CHAR = 1, 387 | WCHAR = -8, 388 | SLONG = 4 + SIGNED_OFFSET, 389 | ULONG = 4 + UNSIGNED_OFFSET, 390 | SSHORT = 5 + SIGNED_OFFSET, 391 | USHORT = 5 + UNSIGNED_OFFSET, 392 | FLOAT = 7, 393 | DOUBLE = 8, 394 | BIT = -7, 395 | STINYINT = -6 + SIGNED_OFFSET, 396 | UTINYINT = -6 + UNSIGNED_OFFSET, 397 | SBIGINT = -5 + SIGNED_OFFSET, 398 | UBIGINT = -5 + UNSIGNED_OFFSET, 399 | BINARY = -2, 400 | TIMESTAMP = 11, 401 | TYPE_DATE = 91, 402 | TYPE_TIME = 92, 403 | TYPE_TIMESTAMP = 93, 404 | NUMERIC = 2, 405 | GUID = -11, 406 | DEFAULT = 99, 407 | ARD_TYPE = -99 408 | ], 409 | // SQL Data Types 410 | SQL_TYPE = [ 411 | // Base data types (sql.h) 412 | UNKNOWN = 0, 413 | NULL = 0, 414 | CHAR = 1, 415 | NUMERIC = 2, 416 | DECIMAL = 3, 417 | INTEGER = 4, 418 | SMALLINT = 5, 419 | FLOAT = 6, 420 | REAL = 7, 421 | DOUBLE = 8, 422 | DATETIME = 9, 423 | // V3 Only 424 | VARCHAR = 12, 425 | // Unicode types (sqlucode.h) 426 | WCHAR = -8, 427 | WVARCHAR = -9, 428 | WLONGVARCHAR = -10, 429 | // Extended data types (sqlext.h) 430 | INTERVAL = 10, 431 | // V3 Only 432 | TIME = 10, 433 | TIMESTAMP = 11, 434 | LONGVARCHAR = -1, 435 | BINARY = -2, 436 | VARBINARY = -3, 437 | LONGVARBINARY = -4, 438 | BIGINT = -5, 439 | TINYINT = -6, 440 | BIT = -7, 441 | GUID = -11, 442 | // V3 Only 443 | // One-parameter shortcuts for date/time data types. 444 | TYPE_DATE = 91, 445 | TYPE_TIME = 92, 446 | TYPE_TIMESTAMP = 93, 447 | TYPE_TIMESTAMP_WITH_TIMEZONE = 95, 448 | // SQL Server Types -150 to -159 (sqlncli.h) 449 | SS_VARIANT = -150, 450 | SS_UDT = -151, 451 | SS_XML = -152, 452 | SS_TABLE = -153, 453 | SS_TIME2 = -154, 454 | SS_TIMESTAMPOFFSET = -155 455 | ], 456 | // SQL_ALL_TYPES = 0, 457 | // static public readonly IntPtr SQL_HANDLE_NULL = IntPtr.Zero; 458 | SQL_LENGTH = [ 459 | SQL_IGNORE = -6, 460 | SQL_DEFAULT_PARAM = -5, 461 | SQL_NO_TOTAL = -4, 462 | SQL_NTS = -3, 463 | SQL_DATA_AT_EXEC = -2, 464 | SQL_NULL_DATA = -1 465 | ], 466 | SQL_DEFAULT_PARAM = -5, 467 | // column ordinals for SQLProcedureColumns result set 468 | // this column ordinals are not defined in any c/c++ header but in the ODBC Programmer's Reference under SQLProcedureColumns 469 | // 470 | COLUMN_NAME = 4, 471 | COLUMN_TYPE = 5, 472 | DATA_TYPE = 6, 473 | COLUMN_SIZE = 8, 474 | DECIMAL_DIGITS = 10, 475 | NUM_PREC_RADIX = 11, 476 | SQL_ATTR = [ 477 | ODBC_VERSION = 200, 478 | CONNECTION_POOLING = 201, 479 | AUTOCOMMIT = 102, 480 | TXN_ISOLATION = 108, 481 | CURRENT_CATALOG = 109, 482 | LOGIN_TIMEOUT = 103, 483 | QUERY_TIMEOUT = 0, 484 | CONNECTION_DEAD = 1209, 485 | SQL_COPT_SS_BASE = 1200, 486 | SQL_COPT_SS_ENLIST_IN_DTC = (1200 + 7), 487 | SQL_COPT_SS_TXN_ISOLATION = (1200 + 27), 488 | MAX_LENGTH = 3, 489 | ROW_BIND_TYPE = 5, 490 | CURSOR_TYPE = 6, 491 | RETRIEVE_DATA = 11, 492 | ROW_STATUS_PTR = 25, 493 | ROWS_FETCHED_PTR = 26, 494 | ROW_ARRAY_SIZE = 27, 495 | // ODBC 3.0 496 | APP_ROW_DESC = 10010, 497 | APP_PARAM_DESC = 10011, 498 | IMP_ROW_DESC = 10012, 499 | IMP_PARAM_DESC = 10013, 500 | METADATA_ID = 10014, 501 | // ODBC 4.0 502 | PRIVATE_DRIVER_LOCATION = 204 503 | ], 504 | SQL_RD = [ 505 | OFF = 0, 506 | ON = 1 507 | ], 508 | SQL_GD = [ 509 | // None = 0, 510 | ANY_COLUMN = 1, 511 | ANY_ORDER = 2, 512 | BLOCK = 4, 513 | BOUND = 8, 514 | OUTPUT_PARAMS = 16 515 | ], 516 | // SQLGetInfo 517 | /* 518 | SQL_INFO = 519 | [ 520 | SQL_ACTIVE_CONNECTIONS = 0, 521 | SQL_MAX_DRIVER_CONNECTIONS = 0, 522 | SQL_MAX_CONCURRENT_ACTIVITIES = 1, 523 | SQL_ACTIVE_STATEMENTS = 1, 524 | SQL_DATA_SOURCE_NAME = 2, 525 | SQL_DRIVER_HDBC, 526 | SQL_DRIVER_HENV, 527 | SQL_DRIVER_HSTMT, 528 | SQL_DRIVER_NAME, 529 | SQL_DRIVER_VER, 530 | SQL_FETCH_DIRECTION, 531 | SQL_ODBC_API_CONFORMANCE, 532 | SQL_ODBC_VER, 533 | SQL_ROW_UPDATES, 534 | SQL_ODBC_SAG_CLI_CONFORMANCE, 535 | SQL_SERVER_NAME, 536 | SQL_SEARCH_PATTERN_ESCAPE, 537 | SQL_ODBC_SQL_CONFORMANCE, 538 | 539 | SQL_DATABASE_NAME, 540 | SQL_DBMS_NAME, 541 | SQL_DBMS_VER, 542 | 543 | SQL_ACCESSIBLE_TABLES, 544 | SQL_ACCESSIBLE_PROCEDURES, 545 | SQL_PROCEDURES, 546 | SQL_CONCAT_NULL_BEHAVIOR, 547 | SQL_CURSOR_COMMIT_BEHAVIOR, 548 | SQL_CURSOR_ROLLBACK_BEHAVIOR, 549 | SQL_DATA_SOURCE_READ_ONLY, 550 | SQL_DEFAULT_TXN_ISOLATION, 551 | SQL_EXPRESSIONS_IN_ORDERBY, 552 | SQL_IDENTIFIER_CASE, 553 | SQL_IDENTIFIER_QUOTE_CHAR, 554 | SQL_MAX_COLUMN_NAME_LEN, 555 | SQL_MAX_CURSOR_NAME_LEN, 556 | SQL_MAX_OWNER_NAME_LEN, 557 | SQL_MAX_SCHEMA_NAME_LEN = 32, 558 | SQL_MAX_PROCEDURE_NAME_LEN, 559 | SQL_MAX_QUALIFIER_NAME_LEN, 560 | SQL_MAX_CATALOG_NAME_LEN = 34, 561 | SQL_MAX_TABLE_NAME_LEN, 562 | SQL_MULT_RESULT_SETS, 563 | SQL_MULTIPLE_ACTIVE_TXN, 564 | SQL_OUTER_JOINS, 565 | SQL_SCHEMA_TERM, 566 | SQL_PROCEDURE_TERM, 567 | SQL_CATALOG_NAME_SEPARATOR, 568 | SQL_CATALOG_TERM, 569 | SQL_SCROLL_CONCURRENCY, 570 | SQL_SCROLL_OPTIONS, 571 | SQL_TABLE_TERM, 572 | SQL_TXN_CAPABLE, 573 | SQL_USER_NAME, 574 | 575 | SQL_CONVERT_FUNCTIONS, 576 | SQL_NUMERIC_FUNCTIONS, 577 | SQL_STRING_FUNCTIONS, 578 | SQL_SYSTEM_FUNCTIONS, 579 | SQL_TIMEDATE_FUNCTIONS, 580 | 581 | SQL_CONVERT_BIGINT, 582 | SQL_CONVERT_BINARY, 583 | SQL_CONVERT_BIT, 584 | SQL_CONVERT_CHAR, 585 | SQL_CONVERT_DATE, 586 | SQL_CONVERT_DECIMAL, 587 | SQL_CONVERT_DOUBLE, 588 | SQL_CONVERT_FLOAT, 589 | SQL_CONVERT_INTEGER, 590 | SQL_CONVERT_LONGVARCHAR, 591 | SQL_CONVERT_NUMERIC, 592 | SQL_CONVERT_REAL, 593 | SQL_CONVERT_SMALLINT, 594 | SQL_CONVERT_TIME, 595 | SQL_CONVERT_TIMESTAMP, 596 | SQL_CONVERT_TINYINT, 597 | SQL_CONVERT_VARBINARY, 598 | SQL_CONVERT_VARCHAR, 599 | SQL_CONVERT_LONGVARBINARY, 600 | 601 | SQL_TXN_ISOLATION_OPTION, 602 | SQL_ODBC_SQL_OPT_IEF, 603 | SQL_INTEGRITY = 73, 604 | SQL_CORRELATION_NAME, 605 | SQL_NON_NULLABLE_COLUMNS, 606 | SQL_DRIVER_HLIB, 607 | SQL_DRIVER_ODBC_VER, 608 | SQL_LOCK_TYPES, 609 | SQL_POS_OPERATIONS, 610 | SQL_POSITIONED_STATEMENTS, 611 | SQL_GETDATA_EXTENSIONS, 612 | SQL_BOOKMARK_PERSISTENCE, 613 | SQL_STATIC_SENSITIVITY, 614 | SQL_FILE_USAGE, 615 | SQL_NULL_COLLATION, 616 | SQL_ALTER_TABLE, 617 | SQL_COLUMN_ALIAS, 618 | SQL_GROUP_BY, 619 | SQL_KEYWORDS, 620 | SQL_ORDER_BY_COLUMNS_IN_SELECT, 621 | SQL_SCHEMA_USAGE, 622 | SQL_CATALOG_USAGE, 623 | SQL_QUOTED_IDENTIFIER_CASE, 624 | SQL_SPECIAL_CHARACTERS, 625 | SQL_SUBQUERIES, 626 | SQL_UNION_STATEMENT, 627 | SQL_MAX_COLUMNS_IN_GROUP_BY, 628 | SQL_MAX_COLUMNS_IN_INDEX, 629 | SQL_MAX_COLUMNS_IN_ORDER_BY, 630 | SQL_MAX_COLUMNS_IN_SELECT, 631 | SQL_MAX_COLUMNS_IN_TABLE, 632 | SQL_MAX_INDEX_SIZE, 633 | SQL_MAX_ROW_SIZE_INCLUDES_LONG, 634 | SQL_MAX_ROW_SIZE, 635 | SQL_MAX_STATEMENT_LEN, 636 | SQL_MAX_TABLES_IN_SELECT, 637 | SQL_MAX_USER_NAME_LEN, 638 | SQL_MAX_CHAR_LITERAL_LEN, 639 | SQL_TIMEDATE_ADD_INTERVALS, 640 | SQL_TIMEDATE_DIFF_INTERVALS, 641 | SQL_NEED_LONG_DATA_LEN, 642 | SQL_MAX_BINARY_LITERAL_LEN, 643 | SQL_LIKE_ESCAPE_CLAUSE, 644 | SQL_CATALOG_LOCATION, 645 | SQL_OJ_CAPABILITIES, 646 | 647 | SQL_ACTIVE_ENVIRONMENTS, 648 | SQL_ALTER_DOMAIN, 649 | SQL_SQL_CONFORMANCE, 650 | SQL_DATETIME_LITERALS, 651 | SQL_BATCH_ROW_COUNT, 652 | SQL_BATCH_SUPPORT, 653 | SQL_CONVERT_WCHAR, 654 | SQL_CONVERT_INTERVAL_DAY_TIME, 655 | SQL_CONVERT_INTERVAL_YEAR_MONTH, 656 | SQL_CONVERT_WLONGVARCHAR, 657 | SQL_CONVERT_WVARCHAR, 658 | SQL_CREATE_ASSERTION, 659 | SQL_CREATE_CHARACTER_SET, 660 | SQL_CREATE_COLLATION, 661 | SQL_CREATE_DOMAIN, 662 | SQL_CREATE_SCHEMA, 663 | SQL_CREATE_TABLE, 664 | SQL_CREATE_TRANSLATION, 665 | SQL_CREATE_VIEW, 666 | SQL_DRIVER_HDESC, 667 | SQL_DROP_ASSERTION, 668 | SQL_DROP_CHARACTER_SET, 669 | SQL_DROP_COLLATION, 670 | SQL_DROP_DOMAIN, 671 | SQL_DROP_SCHEMA, 672 | SQL_DROP_TABLE, 673 | SQL_DROP_TRANSLATION, 674 | SQL_DROP_VIEW, 675 | SQL_DYNAMIC_CURSOR_ATTRIBUTES1, 676 | SQL_DYNAMIC_CURSOR_ATTRIBUTES2, 677 | SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1, 678 | SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2, 679 | SQL_INDEX_KEYWORDS, 680 | SQL_INFO_SCHEMA_VIEWS, 681 | SQL_KEYSET_CURSOR_ATTRIBUTES1, 682 | SQL_KEYSET_CURSOR_ATTRIBUTES2, 683 | SQL_ODBC_INTERFACE_CONFORMANCE, 684 | SQL_PARAM_ARRAY_ROW_COUNTS, 685 | SQL_PARAM_ARRAY_SELECTS, 686 | SQL_SQL92_DATETIME_FUNCTIONS, 687 | SQL_SQL92_FOREIGN_KEY_DELETE_RULE, 688 | SQL_SQL92_FOREIGN_KEY_UPDATE_RULE, 689 | SQL_SQL92_GRANT, 690 | SQL_SQL92_NUMERIC_VALUE_FUNCTIONS, 691 | SQL_SQL92_PREDICATES, 692 | SQL_SQL92_RELATIONAL_JOIN_OPERATORS, 693 | SQL_SQL92_REVOKE, 694 | SQL_SQL92_ROW_VALUE_CONSTRUCTOR, 695 | SQL_SQL92_STRING_FUNCTIONS, 696 | SQL_SQL92_VALUE_EXPRESSIONS, 697 | SQL_STANDARD_CLI_CONFORMANCE, 698 | SQL_STATIC_CURSOR_ATTRIBUTES1, 699 | SQL_STATIC_CURSOR_ATTRIBUTES2, 700 | SQL_AGGREGATE_FUNCTIONS, 701 | SQL_DDL_INDEX, 702 | SQL_DM_VER, 703 | SQL_INSERT_STATEMENT, 704 | SQL_CONVERT_GUID, 705 | 706 | SQL_XOPEN_CLI_YEAR = 10000, 707 | SQL_CURSOR_SENSITIVITY, 708 | SQL_DESCRIBE_PARAMETER, 709 | SQL_CATALOG_NAME, 710 | SQL_COLLATION_SEQ, 711 | SQL_MAX_IDENTIFIER_LEN, 712 | SQL_ASYNC_MODE = 10021, 713 | SQL_MAX_ASYNC_CONCURRENT_STATEMENTS, 714 | 715 | SQL_DTC_TRANSITION_COST = 1750, 716 | ], 717 | */ 718 | SQL_OAC = [ 719 | SQL_OAC_None = 0x0000, 720 | SQL_OAC_LEVEL1 = 0x0001, 721 | SQL_OAC_LEVEL2 = 0x0002 722 | ], 723 | SQL_OSC = [ 724 | SQL_OSC_MINIMUM = 0x0000, 725 | SQL_OSC_CORE = 0x0001, 726 | SQL_OSC_EXTENDED = 0x0002 727 | ], 728 | SQL_SCC = [ 729 | SQL_SCC_XOPEN_CLI_VERSION1 = 0x00000001, 730 | SQL_SCC_ISO92_CLI = 0x00000002 731 | ], 732 | SQL_SVE = [ 733 | SQL_SVE_CASE = 0x00000001, 734 | SQL_SVE_CAST = 0x00000002, 735 | SQL_SVE_COALESCE = 0x00000004, 736 | SQL_SVE_NULLIF = 0x00000008 737 | ], 738 | SQL_SSF = [ 739 | SQL_SSF_CONVERT = 0x00000001, 740 | SQL_SSF_LOWER = 0x00000002, 741 | SQL_SSF_UPPER = 0x00000004, 742 | SQL_SSF_SUBSTRING = 0x00000008, 743 | SQL_SSF_TRANSLATE = 0x00000010, 744 | SQL_SSF_TRIM_BOTH = 0x00000020, 745 | SQL_SSF_TRIM_LEADING = 0x00000040, 746 | SQL_SSF_TRIM_TRAILING = 0x00000080 747 | ], 748 | SQL_SP = [ 749 | // None = 0, 750 | SQL_SP_EXISTS = 0x00000001, 751 | SQL_SP_ISNOTNULL = 0x00000002, 752 | SQL_SP_ISNULL = 0x00000004, 753 | SQL_SP_MATCH_FULL = 0x00000008, 754 | SQL_SP_MATCH_PARTIAL = 0x00000010, 755 | SQL_SP_MATCH_UNIQUE_FULL = 0x00000020, 756 | SQL_SP_MATCH_UNIQUE_PARTIAL = 0x00000040, 757 | SQL_SP_OVERLAPS = 0x00000080, 758 | SQL_SP_UNIQUE = 0x00000100, 759 | SQL_SP_LIKE = 0x00000200, 760 | SQL_SP_IN = 0x00000400, 761 | SQL_SP_BETWEEN = 0x00000800, 762 | SQL_SP_COMPARISON = 0x00001000, 763 | SQL_SP_QUANTIFIED_COMPARISON = 0x00002000, 764 | All = 0x0000FFFF 765 | ], 766 | SQL_OIC = [ 767 | SQL_OIC_CORE = 1, 768 | SQL_OIC_LEVEL1 = 2, 769 | SQL_OIC_LEVEL2 = 3 770 | ], 771 | SQL_USAGE = [ 772 | SQL_U_DML_STATEMENTS = 0x00000001, 773 | SQL_U_PROCEDURE_INVOCATION = 0x00000002, 774 | SQL_U_TABLE_DEFINITION = 0x00000004, 775 | SQL_U_INDEX_DEFINITION = 0x00000008, 776 | SQL_U_PRIVILEGE_DEFINITION = 0x00000010 777 | ], 778 | SQL_GB = [ 779 | SQL_GB_NOT_SUPPORTED = 0, 780 | SQL_GB_GROUP_BY_EQUALS_SELECT = 1, 781 | SQL_GB_GROUP_BY_CONTAINS_SELECT = 2, 782 | SQL_GB_NO_RELATION = 3, 783 | SQL_GB_COLLATE = 4 784 | ], 785 | SQL_NC = [ 786 | SQL_NC_END = 0, 787 | SQL_NC_HIGH = 1, 788 | SQL_NC_LOW = 2, 789 | SQL_NC_START = 3 790 | ], 791 | SQL_CN = [ 792 | SQL_CN_None = 0, 793 | SQL_CN_DIFFERENT = 1, 794 | SQL_CN_ANY = 2 795 | ], 796 | SQL_NNC = [ 797 | SQL_NNC_NULL = 0, 798 | SQL_NNC_NON_NULL = 1 799 | ], 800 | SQL_CB = [ 801 | SQL_CB_NULL = 0, 802 | SQL_CB_NON_NULL = 1 803 | ], 804 | SQL_FD_FETCH = [ 805 | SQL_FD_FETCH_NEXT = 0x00000001, 806 | SQL_FD_FETCH_FIRST = 0x00000002, 807 | SQL_FD_FETCH_LAST = 0x00000004, 808 | SQL_FD_FETCH_PRIOR = 0x00000008, 809 | SQL_FD_FETCH_ABSOLUTE = 0x00000010, 810 | SQL_FD_FETCH_RELATIVE = 0x00000020, 811 | SQL_FD_FETCH_BOOKMARK = 0x00000080 812 | ], 813 | SQL_SQ = [ 814 | SQL_SQ_COMPARISON = 0x00000001, 815 | SQL_SQ_EXISTS = 0x00000002, 816 | SQL_SQ_IN = 0x00000004, 817 | SQL_SQ_QUANTIFIED = 0x00000008, 818 | SQL_SQ_CORRELATED_SUBQUERIES = 0x00000010 819 | ], 820 | SQL_U = [ 821 | SQL_U_UNION = 0x00000001, 822 | SQL_U_UNION_ALL = 0x00000002 823 | ], 824 | SQL_BP = [ 825 | SQL_BP_CLOSE = 0x00000001, 826 | SQL_BP_DELETE = 0x00000002, 827 | SQL_BP_DROP = 0x00000004, 828 | SQL_BP_TRANSACTION = 0x00000008, 829 | SQL_BP_UPDATE = 0x00000010, 830 | SQL_BP_OTHER_HSTMT = 0x00000020, 831 | SQL_BP_SCROLL = 0x00000040 832 | ], 833 | SQL_QL = [ 834 | SQL_QL_START = 0x0001, 835 | SQL_QL_END = 0x0002 836 | ], 837 | SQL_OJ = [ 838 | SQL_OJ_LEFT = 0x00000001, 839 | SQL_OJ_RIGHT = 0x00000002, 840 | SQL_OJ_FULL = 0x00000004, 841 | SQL_OJ_NESTED = 0x00000008, 842 | SQL_OJ_NOT_ORDERED = 0x00000010, 843 | SQL_OJ_INNER = 0x00000020, 844 | SQL_OJ_ALL_COMPARISON_OPS = 0x00000040 845 | ], 846 | SQL_FN_CVT = [ 847 | // None = 0, 848 | SQL_FN_CVT_CONVERT = 0x00000001, 849 | SQL_FN_CVT_CAST = 0x00000002 850 | ], 851 | SQL_FN_NUM = [ 852 | // None = 0, 853 | SQL_FN_NUM_ABS = 0x00000001, 854 | SQL_FN_NUM_ACOS = 0x00000002, 855 | SQL_FN_NUM_ASIN = 0x00000004, 856 | SQL_FN_NUM_ATAN = 0x00000008, 857 | SQL_FN_NUM_ATAN2 = 0x00000010, 858 | SQL_FN_NUM_CEILING = 0x00000020, 859 | SQL_FN_NUM_COS = 0x00000040, 860 | SQL_FN_NUM_COT = 0x00000080, 861 | SQL_FN_NUM_EXP = 0x00000100, 862 | SQL_FN_NUM_FLOOR = 0x00000200, 863 | SQL_FN_NUM_LOG = 0x00000400, 864 | SQL_FN_NUM_MOD = 0x00000800, 865 | SQL_FN_NUM_SIGN = 0x00001000, 866 | SQL_FN_NUM_SIN = 0x00002000, 867 | SQL_FN_NUM_SQRT = 0x00004000, 868 | SQL_FN_NUM_TAN = 0x00008000, 869 | SQL_FN_NUM_PI = 0x00010000, 870 | SQL_FN_NUM_RAND = 0x00020000, 871 | SQL_FN_NUM_DEGREES = 0x00040000, 872 | SQL_FN_NUM_LOG10 = 0x00080000, 873 | SQL_FN_NUM_POWER = 0x00100000, 874 | SQL_FN_NUM_RADIANS = 0x00200000, 875 | SQL_FN_NUM_ROUND = 0x00400000, 876 | SQL_FN_NUM_TRUNCATE = 0x00800000 877 | ], 878 | SQL_SNVF = [ 879 | SQL_SNVF_BIT_LENGTH = 0x00000001, 880 | SQL_SNVF_CHAR_LENGTH = 0x00000002, 881 | SQL_SNVF_CHARACTER_LENGTH = 0x00000004, 882 | SQL_SNVF_EXTRACT = 0x00000008, 883 | SQL_SNVF_OCTET_LENGTH = 0x00000010, 884 | SQL_SNVF_POSITION = 0x00000020 885 | ], 886 | SQL_FN_STR = [ 887 | // None = 0, 888 | SQL_FN_STR_CONCAT = 0x00000001, 889 | SQL_FN_STR_INSERT = 0x00000002, 890 | SQL_FN_STR_LEFT = 0x00000004, 891 | SQL_FN_STR_LTRIM = 0x00000008, 892 | SQL_FN_STR_LENGTH = 0x00000010, 893 | SQL_FN_STR_LOCATE = 0x00000020, 894 | SQL_FN_STR_LCASE = 0x00000040, 895 | SQL_FN_STR_REPEAT = 0x00000080, 896 | SQL_FN_STR_REPLACE = 0x00000100, 897 | SQL_FN_STR_RIGHT = 0x00000200, 898 | SQL_FN_STR_RTRIM = 0x00000400, 899 | SQL_FN_STR_SUBSTRING = 0x00000800, 900 | SQL_FN_STR_UCASE = 0x00001000, 901 | SQL_FN_STR_ASCII = 0x00002000, 902 | SQL_FN_STR_CHAR = 0x00004000, 903 | SQL_FN_STR_DIFFERENCE = 0x00008000, 904 | SQL_FN_STR_LOCATE_2 = 0x00010000, 905 | SQL_FN_STR_SOUNDEX = 0x00020000, 906 | SQL_FN_STR_SPACE = 0x00040000, 907 | SQL_FN_STR_BIT_LENGTH = 0x00080000, 908 | SQL_FN_STR_CHAR_LENGTH = 0x00100000, 909 | SQL_FN_STR_CHARACTER_LENGTH = 0x00200000, 910 | SQL_FN_STR_OCTET_LENGTH = 0x00400000, 911 | SQL_FN_STR_POSITION = 0x00800000 912 | ], 913 | SQL_FN_SYSTEM = [ 914 | // None = 0, 915 | SQL_FN_SYS_USERNAME = 0x00000001, 916 | SQL_FN_SYS_DBNAME = 0x00000002, 917 | SQL_FN_SYS_IFNULL = 0x00000004 918 | ], 919 | SQL_FN_TD = [ 920 | // None = 0, 921 | SQL_FN_TD_NOW = 0x00000001, 922 | SQL_FN_TD_CURDATE = 0x00000002, 923 | SQL_FN_TD_DAYOFMONTH = 0x00000004, 924 | SQL_FN_TD_DAYOFWEEK = 0x00000008, 925 | SQL_FN_TD_DAYOFYEAR = 0x00000010, 926 | SQL_FN_TD_MONTH = 0x00000020, 927 | SQL_FN_TD_QUARTER = 0x00000040, 928 | SQL_FN_TD_WEEK = 0x00000080, 929 | SQL_FN_TD_YEAR = 0x00000100, 930 | SQL_FN_TD_CURTIME = 0x00000200, 931 | SQL_FN_TD_HOUR = 0x00000400, 932 | SQL_FN_TD_MINUTE = 0x00000800, 933 | SQL_FN_TD_SECOND = 0x00001000, 934 | SQL_FN_TD_TIMESTAMPADD = 0x00002000, 935 | SQL_FN_TD_TIMESTAMPDIFF = 0x00004000, 936 | SQL_FN_TD_DAYNAME = 0x00008000, 937 | SQL_FN_TD_MONTHNAME = 0x00010000, 938 | SQL_FN_TD_CURRENT_DATE = 0x00020000, 939 | SQL_FN_TD_CURRENT_TIME = 0x00040000, 940 | SQL_FN_TD_CURRENT_TIMESTAMP = 0x00080000, 941 | SQL_FN_TD_EXTRACT = 0x00100000 942 | ], 943 | SQL_SDF = [ 944 | SQL_SDF_CURRENT_DATE = 0x00000001, 945 | SQL_SDF_CURRENT_TIME = 0x00000002, 946 | SQL_SDF_CURRENT_TIMESTAMP = 0x00000004 947 | ], 948 | SQL_TSI = [ 949 | // None = 0, 950 | SQL_TSI_FRAC_SECOND = 0x00000001, 951 | SQL_TSI_SECOND = 0x00000002, 952 | SQL_TSI_MINUTE = 0x00000004, 953 | SQL_TSI_HOUR = 0x00000008, 954 | SQL_TSI_DAY = 0x00000010, 955 | SQL_TSI_WEEK = 0x00000020, 956 | SQL_TSI_MONTH = 0x00000040, 957 | SQL_TSI_QUARTER = 0x00000080, 958 | SQL_TSI_YEAR = 0x00000100 959 | ], 960 | SQL_AF = [ 961 | // None = 0, 962 | SQL_AF_AVG = 0x00000001, 963 | SQL_AF_COUNT = 0x00000002, 964 | SQL_AF_MAX = 0x00000004, 965 | SQL_AF_MIN = 0x00000008, 966 | SQL_AF_SUM = 0x00000010, 967 | SQL_AF_DISTINCT = 0x00000020, 968 | SQL_AF_ALL = 0x00000040, 969 | All = 0xFF 970 | ], 971 | SQL_SC = [ 972 | // None = 0, 973 | SQL_SC_SQL92_ENTRY = 0x00000001, 974 | SQL_SC_FIPS127_2_TRANSITIONAL = 0x00000002, 975 | SQL_SC_SQL92_INTERMEDIATE = 0x00000004, 976 | SQL_SC_SQL92_FULL = 0x00000008 977 | ], 978 | SQL_DL_SQL92 = [ 979 | SQL_DL_SQL92_DATE = 0x00000001, 980 | SQL_DL_SQL92_TIME = 0x00000002, 981 | SQL_DL_SQL92_TIMESTAMP = 0x00000004, 982 | SQL_DL_SQL92_INTERVAL_YEAR = 0x00000008, 983 | SQL_DL_SQL92_INTERVAL_MONTH = 0x00000010, 984 | SQL_DL_SQL92_INTERVAL_DAY = 0x00000020, 985 | SQL_DL_SQL92_INTERVAL_HOUR = 0x00000040, 986 | SQL_DL_SQL92_INTERVAL_MINUTE = 0x00000080, 987 | SQL_DL_SQL92_INTERVAL_SECOND = 0x00000100, 988 | SQL_DL_SQL92_INTERVAL_YEAR_TO_MONTH = 0x00000200, 989 | SQL_DL_SQL92_INTERVAL_DAY_TO_HOUR = 0x00000400, 990 | SQL_DL_SQL92_INTERVAL_DAY_TO_MINUTE = 0x00000800, 991 | SQL_DL_SQL92_INTERVAL_DAY_TO_SECOND = 0x00001000, 992 | SQL_DL_SQL92_INTERVAL_HOUR_TO_MINUTE = 0x00002000, 993 | SQL_DL_SQL92_INTERVAL_HOUR_TO_SECOND = 0x00004000, 994 | SQL_DL_SQL92_INTERVAL_MINUTE_TO_SECOND = 0x00008000 995 | ], 996 | SQL_IK = [ 997 | SQL_IK_NONE = 0x00000000, 998 | SQL_IK_ASC = 0x00000001, 999 | SQL_IK_DESC = 0x00000002, 1000 | // SQL_IK_ASC | SQL_IK_DESC 1001 | SQL_IK_ALL = 0x00000003 1002 | ], 1003 | SQL_ISV = [ 1004 | SQL_ISV_ASSERTIONS = 0x00000001, 1005 | SQL_ISV_CHARACTER_SETS = 0x00000002, 1006 | SQL_ISV_CHECK_CONSTRAINTS = 0x00000004, 1007 | SQL_ISV_COLLATIONS = 0x00000008, 1008 | SQL_ISV_COLUMN_DOMAIN_USAGE = 0x00000010, 1009 | SQL_ISV_COLUMN_PRIVILEGES = 0x00000020, 1010 | SQL_ISV_COLUMNS = 0x00000040, 1011 | SQL_ISV_CONSTRAINT_COLUMN_USAGE = 0x00000080, 1012 | SQL_ISV_CONSTRAINT_TABLE_USAGE = 0x00000100, 1013 | SQL_ISV_DOMAIN_CONSTRAINTS = 0x00000200, 1014 | SQL_ISV_DOMAINS = 0x00000400, 1015 | SQL_ISV_KEY_COLUMN_USAGE = 0x00000800, 1016 | SQL_ISV_REFERENTIAL_CONSTRAINTS = 0x00001000, 1017 | SQL_ISV_SCHEMATA = 0x00002000, 1018 | SQL_ISV_SQL_LANGUAGES = 0x00004000, 1019 | SQL_ISV_TABLE_CONSTRAINTS = 0x00008000, 1020 | SQL_ISV_TABLE_PRIVILEGES = 0x00010000, 1021 | SQL_ISV_TABLES = 0x00020000, 1022 | SQL_ISV_TRANSLATIONS = 0x00040000, 1023 | SQL_ISV_USAGE_PRIVILEGES = 0x00080000, 1024 | SQL_ISV_VIEW_COLUMN_USAGE = 0x00100000, 1025 | SQL_ISV_VIEW_TABLE_USAGE = 0x00200000, 1026 | SQL_ISV_VIEWS = 0x00400000 1027 | ], 1028 | SQL_SRJO = [ 1029 | // None = 0, 1030 | SQL_SRJO_CORRESPONDING_CLAUSE = 0x00000001, 1031 | SQL_SRJO_CROSS_JOIN = 0x00000002, 1032 | SQL_SRJO_EXCEPT_JOIN = 0x00000004, 1033 | SQL_SRJO_FULL_OUTER_JOIN = 0x00000008, 1034 | SQL_SRJO_INNER_JOIN = 0x00000010, 1035 | SQL_SRJO_INTERSECT_JOIN = 0x00000020, 1036 | SQL_SRJO_LEFT_OUTER_JOIN = 0x00000040, 1037 | SQL_SRJO_NATURAL_JOIN = 0x00000080, 1038 | SQL_SRJO_RIGHT_OUTER_JOIN = 0x00000100, 1039 | SQL_SRJO_UNION_JOIN = 0x00000200 1040 | ], 1041 | SQL_SRVC = [ 1042 | SQL_SRVC_VALUE_EXPRESSION = 0x00000001, 1043 | SQL_SRVC_NULL = 0x00000002, 1044 | SQL_SRVC_DEFAULT = 0x00000004, 1045 | SQL_SRVC_ROW_SUBQUERY = 0x00000008 1046 | ], 1047 | // public static readonly int SQL_OV_ODBC3 = 3; 1048 | // public const Int32 SQL_NTS = -3; 1049 | // flags for null-terminated string 1050 | // Pooling 1051 | SQL_CP = [ 1052 | OFF = 0, 1053 | ONE_PER_DRIVER = 1, 1054 | ONE_PER_HENV = 2 1055 | ], 1056 | /* 1057 | public const Int32 SQL_CD_TRUE = 1; 1058 | public const Int32 SQL_CD_FALSE = 0; 1059 | 1060 | public const Int32 SQL_DTC_DONE = 0; 1061 | public const Int32 SQL_IS_POINTER = -4; 1062 | public const Int32 SQL_IS_PTR = 1; 1063 | */ 1064 | SQL_DRIVER = [ 1065 | NOPROMPT = 0, 1066 | COMPLETE = 1, 1067 | PROMPT = 2, 1068 | COMPLETE_REQUIRED = 3 1069 | ], 1070 | // Column set for SQLPrimaryKeys 1071 | SQL_PRIMARYKEYS = [ 1072 | /* 1073 | // TABLE_CAT 1074 | CATALOGNAME = 1, 1075 | // TABLE_SCHEM 1076 | SCHEMANAME = 2, 1077 | // TABLE_NAME 1078 | TABLENAME = 3, 1079 | */ 1080 | // COLUMN_NAME 1081 | COLUMNNAME = 4 1082 | /* 1083 | // KEY_SEQ 1084 | KEY_SEQ = 5, 1085 | // PK_NAME 1086 | PKNAME = 6, 1087 | */ 1088 | ], 1089 | // Column set for SQLStatistics 1090 | SQL_STATISTICS = [ 1091 | /* 1092 | // TABLE_CAT 1093 | CATALOGNAME = 1, 1094 | // TABLE_SCHEM 1095 | SCHEMANAME = 2, 1096 | // TABLE_NAME 1097 | TABLENAME = 3, 1098 | // NON_UNIQUE 1099 | NONUNIQUE = 4, 1100 | // INDEX_QUALIFIER 1101 | INDEXQUALIFIER = 5, 1102 | */ 1103 | // INDEX_NAME 1104 | INDEXNAME = 6, 1105 | /* 1106 | // TYPE 1107 | TYPE = 7, 1108 | */ 1109 | // ORDINAL_POSITION 1110 | ORDINAL_POSITION = 8, 1111 | // COLUMN_NAME 1112 | COLUMN_NAME = 9 1113 | /* 1114 | // ASC_OR_DESC 1115 | ASC_OR_DESC = 10, 1116 | // CARDINALITY 1117 | CARDINALITY = 11, 1118 | // PAGES 1119 | PAGES = 12, 1120 | // FILTER_CONDITION 1121 | FILTER_CONDITION = 13, 1122 | */ 1123 | ], 1124 | // Column set for SQLSpecialColumns 1125 | SQL_SPECIALCOLUMNSET = [ 1126 | /* 1127 | // SCOPE 1128 | SCOPE = 1, 1129 | */ 1130 | // COLUMN_NAME 1131 | COLUMN_NAME = 2 1132 | /* 1133 | // DATA_TYPE 1134 | DATA_TYPE = 3, 1135 | // TYPE_NAME 1136 | TYPE_NAME = 4, 1137 | // COLUMN_SIZE 1138 | COLUMN_SIZE = 5, 1139 | // BUFFER_LENGTH 1140 | BUFFER_LENGTH = 6, 1141 | // DECIMAL_DIGITS 1142 | DECIMAL_DIGITS = 7, 1143 | // PSEUDO_COLUMN 1144 | PSEUDO_COLUMN = 8, 1145 | */ 1146 | ], 1147 | SQL_DIAG = [ 1148 | CURSOR_ROW_COUNT = -1249, 1149 | ROW_NUMBER = -1248, 1150 | COLUMN_NUMBER = -1247, 1151 | RETURNCODE = 1, 1152 | NUMBER = 2, 1153 | ROW_COUNT = 3, 1154 | SQLSTATE = 4, 1155 | NATIVE = 5, 1156 | MESSAGE_TEXT = 6, 1157 | DYNAMIC_FUNCTION = 7, 1158 | CLASS_ORIGIN = 8, 1159 | SUBCLASS_ORIGIN = 9, 1160 | CONNECTION_NAME = 10, 1161 | SERVER_NAME = 11, 1162 | DYNAMIC_FUNCTION_CODE = 12 1163 | ], 1164 | SQL_SU = [ 1165 | SQL_SU_DML_STATEMENTS = 0x00000001, 1166 | SQL_SU_PROCEDURE_INVOCATION = 0x00000002, 1167 | SQL_SU_TABLE_DEFINITION = 0x00000004, 1168 | SQL_SU_INDEX_DEFINITION = 0x00000008, 1169 | SQL_SU_PRIVILEGE_DEFINITION = 0x00000010 1170 | ] 1171 | ] 1172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DuckDB Power Query Connector by MotherDuck 2 | 3 | This is the Power Query Custom Connector for DuckDB. Use this to connect to a DuckDB database in memory, from a local file or on MotherDuck with Power BI and Excel. 4 | 5 | 1. [Installing](#installing) 6 | 2. [How to use with Power BI](#how-to-use-with-power-bi) 7 | 3. [Turning on UTF-8 support in the Language & Region settings](#turning-on-utf-8-support-in-the-language--region-settings) 8 | 4. [[Experimental] Power BI Service](#experimental-power-bi-service) 9 | 10 | ## Installing 11 | 12 | 1. Download the latest DuckDB ODBC driver from the [DuckDB Power Query Connector GitHub Releases](https://github.com/MotherDuck-Open-Source/duckdb-power-query-connector/releases) for Windows: 13 | - [duckdb_odbc-windows-amd64.zip](https://github.com/MotherDuck-Open-Source/duckdb-power-query-connector/releases/latest/download/duckdb_odbc-windows-amd64.zip) 14 | 1. Extract the `.zip` archive into a permanent location, such as `C:\Program Files\duckdb_odbc`, and install the latest DuckDB driver by running `odbc_install.exe`. 15 | 1. Check that the correct version was installed. To do this, open the Registry Editor by running `regedit` in the command prompt or `Run` dialog. Browse to the `HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\DuckDB Driver` entry and check that the Driver field contains the version you installed. If not, delete the `DuckDB Driver` registry key and rerun the installer. 16 | 17 | 1. Open Power BI, go to File -> Options and settings -> Options -> Security -> Data Extensions. Enable "Allow any extensions to load without validation or warning". 18 | ![Dialog window showing Power BI Options -> Security -> Data Extensions](images/power_bi_options.png) 19 | 20 | 1. Download the latest version of the DuckDB Power Query extension: 21 | - [duckdb-power-query-connector.mez](https://github.com/MotherDuck-Open-Source/duckdb-power-query-connector/releases/latest/download/duckdb-power-query-connector.mez) 22 | 23 | 1. Create this folder if it does not yet exist: `[Documents]\Power BI Desktop\Custom Connectors`. 24 | 25 | 1. Move or copy the `duckdb-power-query-connector.mez` file into `[Documents]\Power BI Desktop\Custom Connectors`. Note that if this location does not work, you may need to place this in your OneDrive Documents folder. 26 | 27 | 28 | ## How to use with Power BI 29 | 30 | 1. Click on Get Data -> More... 31 | 1. Search for `DuckDB` and click "Connect" 32 | ![Find DuckDB connector](images/find-connector.png) 33 | 1. Enter your database location. This can be a local file path (e.g. `~\my_database.db`) or a MotherDuck database location (e.g. `md:my_database`). (Optional) enter your [MotherDuck token](https://app.motherduck.com/token-request?appName=PowerBI). If you want to access the database in `read_only` mode, you can set it to `true`. 34 | ![Connect to your DuckDB database](images/connect-duckdb.png) 35 | 36 | > From version 0.1.7, the `motherduck_token` is a required parameter, in order to enable usage with Power BI Service. If you are using DuckDB, you can enter any value (e.g. `ducks`) into the MotherDuck token field. 37 | 38 | Click "OK". 39 | 1. Click "Connect". 40 | ![Connect dialog](images/connect.png) 41 | 1. Select the table(s) you want to import. Click "Load". 42 | ![Navigator dialog to preview and select your table(s)](images/navigator.png) 43 | 1. You can now query your data and create visualizations! 44 | ![Power BI example usage](images/power-bi-example.png) 45 | 46 | 47 | ## Turning on UTF-8 support in the Language & Region settings 48 | 49 | UTF-8 is currently not supported in the DuckDB ODBC driver. As a workaround, you can turn on UTF-8 decoding in Windows. Note that this may change behavior for other applications, so please use with caution. 50 | 51 | 1. Open start menu and type "Language settings". Open the "Language & region" settings 52 | 2. Click on "Administrative language settings" 53 | 3. Click on "Change system locale" 54 | 4. Check the "Beta: Use Unicode UTF-8 for worldwide language support" and click OK 55 | 56 | ![Screenshot 2024-05-03 165950](https://github.com/MotherDuck-Open-Source/duckdb-power-query-connector/assets/4041805/3251d212-0cde-44be-b3a5-68a0d3d434cf) 57 | 58 | 5. This prompts Windows to restart. 59 | 6. Next, open Power BI, click on "Options and settings" -> "Options" -> "Data Load" and click the "Clear cache" button. 60 | 61 | Now, you should be able to load your UTF-8 encoded database with Power BI directly: 62 | 63 | image 64 | 65 | ## [Experimental] Power BI Service 66 | 67 | To use the [Power BI Service](https://app.powerbi.com/) with DuckDB, you can use the [On-Premises Data Gateway](https://learn.microsoft.com/en-us/power-bi/connect-data/service-gateway-onprem) application. Note that data will only be available when the gateway is on and connected to the internet. This will enable features like automated refresh, and let you share your PowerBI dashboards online. 68 | 69 | 1. Follow these instructions to install the on-premises data gateway: [Download and install a standard gateway](https://learn.microsoft.com/en-us/data-integration/gateway/service-gateway-install#download-and-install-a-standard-gateway). 70 | 2. Open Services, and find the On-premises data gateway service. Double click to open the Properties dialog. 71 | 72 | 73 | 74 | Go to the "Log On" tab and click "Local System Account" and check "Allow service to interact with Desktop". 75 | 76 | 77 | 78 | 3. Click on "Restart" to restart the service. Open the "Connectors" tab and wait until "DuckDB" shows up in Custom Connectors. If it does not, please follow the Custom Connector installation instructions above. 79 | 80 | 81 | 82 | 4. Go to [Power BI Service](https://app.powerbi.com/) and click on the "Settings" icon in the top right. Navigate to the "Manage connections and gateways" page. 83 | 84 | 85 | 86 | 5. Click on the dots menu next to the gateway you configured in step 1. and click on "Settings" 87 | 88 | 89 | 90 | 6. Click the "Allow user's cloud data sources to refresh through this gateway cluster" and "Allow user's custom data connectors to refresh through this gateway cluster." 91 | 92 | 93 | 94 | 7. On the "Manage connections and gateways" page, click on the "Connections" tab and click "+ New" 95 | 96 | 97 | 98 | 8. Enter your connection details. You can enter any local file name here, or use MotherDuck. If you do, make sure to enter a valid [MotherDuck token](https://app.motherduck.com/token-request?appName=powerbi). 99 | 100 | > From version 0.1.7, the `motherduck_token` is a required parameter, in order to enable usage with Power BI Service. If you are using DuckDB, you can enter any value (e.g. `ducks`) into the MotherDuck token field. 101 | 102 | > If you are using MotherDuck, make sure that the MotherDuck extension is installed under `C:\WINDOWS\system32\config\systemprofile\.duckdb\extensions\`. To do so, you can download the [DuckDB CLI client for Windows](https://duckdb.org/docs/installation/?version=stable&environment=cli&platform=win&download_method=direct&architecture=x86_64), and run `INSTALL motherduck`. Then, you can copy over the extension files via PowerShell: 103 | ``` 104 | cp -R '~\.duckdb\extensions\v1.2.0\' "C:\WINDOWS\system32\config\systemprofile\.duckdb\extensions\" 105 | ``` 106 | 107 | > Optionally, you can override the `motherduck_token` field by setting a Username and Password using Basic Authentication. This is useful if you need to replace the token, but don't want to recreate the connection. You can set the Username to any value and the Password to the token value. 108 | 109 | Enable the "Skip test connection" check box. 110 | 111 | 112 | 113 | Click "Create". 114 | 115 | 116 | 117 | 9. Now you can create a Power BI report and publish it to Power BI Service. 118 | On your report, click "Publish". 119 | 120 | 121 | 122 | 10. Done! Now you can click on the URL in the Publishing to Power BI dialog to access the online report. 123 | 124 | 125 | -------------------------------------------------------------------------------- /duckdb-logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-16.png -------------------------------------------------------------------------------- /duckdb-logo-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-20.png -------------------------------------------------------------------------------- /duckdb-logo-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-24.png -------------------------------------------------------------------------------- /duckdb-logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-32.png -------------------------------------------------------------------------------- /duckdb-logo-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-40.png -------------------------------------------------------------------------------- /duckdb-logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-48.png -------------------------------------------------------------------------------- /duckdb-logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-64.png -------------------------------------------------------------------------------- /duckdb-logo-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/duckdb-logo-80.png -------------------------------------------------------------------------------- /images/connect-duckdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/images/connect-duckdb.png -------------------------------------------------------------------------------- /images/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/images/connect.png -------------------------------------------------------------------------------- /images/find-connector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/images/find-connector.png -------------------------------------------------------------------------------- /images/navigator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/images/navigator.png -------------------------------------------------------------------------------- /images/power-bi-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/images/power-bi-example.png -------------------------------------------------------------------------------- /images/power_bi_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motherduckdb/duckdb-power-query-connector/b25bb9d0c84928f8e92de314cb928ca918f9e421/images/power_bi_options.png -------------------------------------------------------------------------------- /resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Connect to DuckDB 122 | 123 | 124 | DuckDB 125 | 126 | 127 | -------------------------------------------------------------------------------- /test/test.sql: -------------------------------------------------------------------------------- 1 | create or replace table test AS ( 2 | select 3 | (random() > 0.5)::BOOL as customer_flag, 4 | x::TINYINT as x, 5 | (random() * 10)::SMALLINT as customer_small_int_field, 6 | (random() * 10)::INTEGER as customer_integer_field, 7 | (random() * 10)::BIGINT as customer_bigint_field, 8 | '2024-03-14'::DATE as customer_date_field, 9 | TIME '01:02:03' as customer_time_field, 10 | '2024-02-01'::TIMESTAMP + interval 1 minute * (random() * 20000)::int as customer_timestamp, 11 | (x * 3.141592653589793238462643)::DECIMAL(27, 24) as customer_decimal_field, 12 | (1e12 * random())::REAL as customer_real_field, 13 | (1e12 * random())::DOUBLE as customer_double_field, 14 | 'hello world ' || CAST(x AS VARCHAR) as customer_varchar_field, 15 | '\xAA\xAB\xAC'::BLOB as customer_blob_field, 16 | x * (INTERVAL 5 HOUR) as customer_interval_field, 17 | (x * 20)::UTINYINT as customer_utinyint_field, 18 | (x * 6000)::USMALLINT as customer_usmallint_field, 19 | (random() * 10e8)::UINTEGER as customer_uinteger_field, 20 | (x * 10e15)::UBIGINT as customer_bigint_field, 21 | '2024-03-14 15:14:15'::TIMESTAMPTZ as customer_timestamptz_field, 22 | -- 10101::BIT as customer_bit_field, 23 | (x * 10e15)::UHUGEINT as customer_uhugeint_field, 24 | (x * 10e10)::HUGEINT as customer_hugeint_field, 25 | gen_random_uuid() as customer_uuid_field, 26 | 27 | from generate_series(1, 10) g(x) 28 | ); 29 | --------------------------------------------------------------------------------