1+ function quoteid (str)
2+ # avoid double quoting
3+ if str[1 ] == ' `' && str[end ] == ' `'
4+ return str
5+ else
6+ return string (' `' , str, ' `' )
7+ end
8+ end
9+
10+ sqltype (:: Type{Union{T, Missing}} ) where {T} = sqltype (T)
11+ sqltype (T) = get (SQLTYPES, T, " VARCHAR(255)" )
12+
13+ const SQLTYPES = Dict {Type, String} (
14+ Int8 => " TINYINT" ,
15+ Int16 => " SMALLINT" ,
16+ Int32 => " INTEGER" ,
17+ Int64 => " BIGINT" ,
18+ UInt8 => " TINYINT UNSIGNED" ,
19+ UInt16 => " SMALLINT UNSIGNED" ,
20+ UInt32 => " INTEGER UNSIGNED" ,
21+ UInt64 => " BIGINT UNSIGNED" ,
22+ Float32 => " FLOAT" ,
23+ Float64 => " DOUBLE" ,
24+ DecFP. Dec64 => " NUMERIC(16, 6)" ,
25+ DecFP. Dec128 => " NUMERIC(35, 6)" ,
26+ Bool => " BOOL" ,
27+ Vector{UInt8} => " BLOB" ,
28+ String => " VARCHAR(255)" ,
29+ Date => " DATE" ,
30+ Time => " TIME" ,
31+ DateTime => " DATETIME" ,
32+ )
33+
34+ checkdupnames (names) = length (unique (map (x-> lowercase (String (x)), names))) == length (names) || error (" duplicate case-insensitive column names detected; sqlite doesn't allow duplicate column names and treats them case insensitive" )
35+
36+ function createtable (conn:: Connection , nm:: AbstractString , sch:: Tables.Schema ; debug:: Bool = false , quoteidentifiers:: Bool = true , createtableclause:: AbstractString = " CREATE TABLE" , columnsuffix= Dict ())
37+ names = sch. names
38+ checkdupnames (names)
39+ types = [sqltype (T) for T in sch. types]
40+ columns = (string (quoteidentifiers ? quoteid (String (names[i])) : names[i], ' ' , types[i], ' ' , get (columnsuffix, names[i], " " )) for i = 1 : length (names))
41+ debug && @info " executing create table statement: `$createtableclause $nm ($(join (columns, " , " )) )`"
42+ return DBInterface. execute (conn, " $createtableclause $nm ($(join (columns, " , " )) )" )
43+ end
44+
45+ """
46+ ODBC.load(table, conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, columnsuffix=Dict(), debug=false)
47+ table |> ODBC.load(conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, columnsuffix=Dict(), debug=false)
48+
49+ Attempts to take a Tables.jl source `table` and load into the database represented by `conn` with table name `name`.
50+
51+ It first detects the `Tables.Schema` of the table source and generates a `CREATE TABLE` statement
52+ with the appropriate column names and types. If no table name is provided, one will be autogenerated, like `odbcjl_xxxxx`.
53+ The `CREATE TABLE` clause can be provided manually by passing the `createtableclause` keyword argument, which
54+ would allow specifying a temporary table or `if not exists`.
55+ Column definitions can also be enhanced by providing arguments to `columnsuffix` as a `Dict` of
56+ column name (given as a `Symbol`) to a string of the enhancement that will come after name and type like
57+ `[column name] [column type] enhancements`. This allows, for example, specifying the charset of a string column
58+ by doing something like `columnsuffix=Dict(:Name => "CHARACTER SET utf8mb4")`.
59+
60+ Do note that databases vary wildly in requirements for `CREATE TABLE` and column definitions
61+ so it can be extremely difficult to load data generically. You may just need to tweak some of the provided
62+ keyword arguments, but you may also need to execute the `CREATE TABLE` and `INSERT` statements
63+ yourself. If you run into issues, you can [open an issue](https://github.com/JuliaDatabases/ODBC.jl/issues) and
64+ we can see if there's something we can do to make it easier to use this function.
65+ """
66+ function load end
67+
68+ load (conn:: Connection , table:: AbstractString = " mysql_" * Random. randstring (5 ); kw... ) = x-> load (x, conn, table; kw... )
69+
70+ function load (itr, conn:: Connection , name:: AbstractString = " mysql_" * Random. randstring (5 ); append:: Bool = true , quoteidentifiers:: Bool = true , debug:: Bool = false , limit:: Integer = typemax (Int64), kw... )
71+ # get data
72+ rows = Tables. rows (itr)
73+ sch = Tables. schema (rows)
74+ if sch === nothing
75+ # we want to ensure we always have a schema, so materialize if needed
76+ rows = Tables. rows (columntable (rows))
77+ sch = Tables. schema (rows)
78+ end
79+ # ensure table exists
80+ if quoteidentifiers
81+ name = quoteid (name)
82+ end
83+ try
84+ createtable (conn, name, sch; quoteidentifiers= quoteidentifiers, debug= debug, kw... )
85+ catch e
86+ @warn " error creating table" (e, catch_backtrace ())
87+ end
88+ if ! append
89+ DBInterface. execute (conn, " DELETE FROM $name " )
90+ end
91+ # start a transaction for inserting rows
92+ transaction (conn) do
93+ params = chop (repeat (" ?," , length (sch. names)))
94+ stmt = DBInterface. prepare (conn, " INSERT INTO $name VALUES ($params )" )
95+ for (i, row) in enumerate (rows)
96+ i > limit && break
97+ debug && @info " inserting row $i ; $(Tables. Row (row)) "
98+ DBInterface. execute (stmt, Tables. Row (row))
99+ end
100+ end
101+
102+ return name
103+ end
104+
105+ function transaction (f:: Function , conn)
106+ API. autocommit (conn. mysql, false )
107+ try
108+ f ()
109+ API. commit (conn. mysql)
110+ catch
111+ API. rollback (conn. mysql)
112+ rethrow ()
113+ finally
114+ API. autocommit (conn. mysql, true )
115+ end
116+ end
0 commit comments