Skip to content

Commit 6f09f0b

Browse files
authored
Merge branch 'main' into main
2 parents 0146f7b + 49a062f commit 6f09f0b

7 files changed

Lines changed: 55 additions & 25 deletions

File tree

.github/workflows/TagBot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ on:
44
types:
55
- created
66
workflow_dispatch:
7+
inputs:
8+
lookback:
9+
default: 3
10+
permissions:
11+
contents: write
712
jobs:
813
TagBot:
914
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
@@ -12,3 +17,4 @@ jobs:
1217
- uses: JuliaRegistries/TagBot@v1
1318
with:
1419
token: ${{ secrets.GITHUB_TOKEN }}
20+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.github/workflows/ci.yml

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,34 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
version:
15-
- 1.0
15+
- "1.6"
1616
- 1 # automatically expands to the latest stable 1.x release of Julia
1717
- nightly
1818
os:
1919
- ubuntu-latest
2020
arch:
2121
- x64
2222
steps:
23-
- uses: actions/checkout@v2
23+
- uses: actions/checkout@v5
2424
- run: docker compose up -d
2525
- uses: julia-actions/setup-julia@v2
2626
with:
2727
version: ${{ matrix.version }}
2828
arch: ${{ matrix.arch }}
29-
- uses: actions/cache@v4
30-
env:
31-
cache-name: cache-artifacts
32-
with:
33-
path: ~/.julia/artifacts
34-
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
35-
restore-keys: |
36-
${{ runner.os }}-test-${{ env.cache-name }}-
37-
${{ runner.os }}-test-
38-
${{ runner.os }}-
29+
- uses: julia-actions/cache@v2
3930
- uses: julia-actions/julia-buildpkg@v1
4031
- uses: julia-actions/julia-runtest@v1
4132
- uses: julia-actions/julia-processcoverage@v1
42-
- uses: codecov/codecov-action@v1
33+
- uses: codecov/codecov-action@v5
4334
with:
44-
file: lcov.info
35+
files: lcov.info
36+
token: ${{ secrets.CODECOV_TOKEN }}
4537
docs:
4638
name: Documentation
4739
runs-on: ubuntu-latest
4840
steps:
49-
- uses: actions/checkout@v2
50-
- uses: julia-actions/setup-julia@v1
41+
- uses: actions/checkout@v5
42+
- uses: julia-actions/setup-julia@v2
5143
with:
5244
version: '1'
5345
- run: |

Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ DBInterface = "2.5"
1818
DecFP = "0.4.9, 0.4.10, 1"
1919
MariaDB_Connector_C_jll = "3.1.12"
2020
Parsers = "0.3, 1, 2"
21-
Random = "1.11.0"
2221
Tables = "1"
2322
julia = "1.6"
2423

src/MySQL.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ function setoptions!(mysql;
130130
passphrase::Union{AbstractString, Nothing}=nothing,
131131
ssl_verify_server_cert::Union{Bool, Nothing}=nothing,
132132
ssl_enforce::Union{Bool, Nothing}=nothing,
133+
ssl_mode::Union{API.mysql_ssl_mode, Nothing}=nothing,
133134
default_auth::Union{AbstractString, Nothing}=nothing,
134135
connection_handler::Union{AbstractString, Nothing}=nothing,
135136
plugin_dir::Union{AbstractString, Nothing}=nothing,
@@ -201,6 +202,9 @@ function setoptions!(mysql;
201202
if ssl_crlpath !== nothing
202203
API.setoption(mysql, API.MYSQL_OPT_SSL_CRLPATH, ssl_crlpath)
203204
end
205+
if ssl_mode !== nothing
206+
API.setoption(mysql, API.MYSQL_OPT_SSL_MODE, ssl_mode)
207+
end
204208
if passphrase !== nothing
205209
API.setoption(mysql, API.MARIADB_OPT_TLS_PASSPHRASE, passphrase)
206210
end

src/api/consts.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ end
140140
MYSQL_PROTOCOL_MEMORY
141141
end
142142

143+
@enum mysql_ssl_mode begin
144+
SSL_MODE_DISABLED
145+
SSL_MODE_PREFERRED
146+
SSL_MODE_REQUIRED
147+
SSL_MODE_VERIFY_CA
148+
SSL_MODE_VERIFY_IDENTITY
149+
end
150+
143151
# Options to be passed to mysql_options API.
144152
@enum mysql_option begin
145153
MYSQL_OPT_CONNECT_TIMEOUT
@@ -212,9 +220,10 @@ end
212220
MARIADB_OPT_INTERACTIVE
213221
MARIADB_OPT_PROXY_HEADER
214222
MARIADB_OPT_IO_WAIT
223+
MYSQL_OPT_SSL_MODE
215224
end
216225

217-
const CUINTOPTS = Set([MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_PROTOCOL, MYSQL_OPT_READ_TIMEOUT, MYSQL_OPT_WRITE_TIMEOUT])
226+
const CUINTOPTS = Set([MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_PROTOCOL, MYSQL_OPT_READ_TIMEOUT, MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_SSL_MODE])
218227
const CULONGOPTS = Set([MYSQL_OPT_MAX_ALLOWED_PACKET, MYSQL_OPT_NET_BUFFER_LENGTH])
219228
const BOOLOPTS = Set([MYSQL_ENABLE_CLEARTEXT_PLUGIN, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, MYSQL_OPT_LOCAL_INFILE, MYSQL_OPT_RECONNECT, MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_SSL_ENFORCE, MYSQL_OPT_SSL_VERIFY_SERVER_CERT])
220229
const STRINGOPTS = Set([MYSQL_DEFAULT_AUTH, MYSQL_OPT_BIND, MYSQL_OPT_SSL_CA, MYSQL_OPT_SSL_CAPATH, MYSQL_OPT_SSL_CERT, MYSQL_OPT_SSL_CIPHER, MYSQL_OPT_SSL_CRL, MYSQL_OPT_SSL_CRLPATH, MYSQL_OPT_SSL_KEY, MYSQL_OPT_TLS_VERSION, MYSQL_PLUGIN_DIR, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, MYSQL_SERVER_PUBLIC_KEY, MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_SHARED_MEMORY_BASE_NAME])

src/load.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ end
99

1010
sqltype(::Type{Union{T, Missing}}) where {T} = sqltype(T)
1111
sqltype(T) = get(SQLTYPES, T, "VARCHAR(255)")
12+
sqltype(T, coltypes, name) = get(coltypes, name, sqltype(T))
1213

1314
const SQLTYPES = Dict{Type, String}(
1415
Int8 => "TINYINT",
@@ -34,27 +35,30 @@ const SQLTYPES = Dict{Type, String}(
3435

3536
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")
3637

37-
function createtable(conn::Connection, nm::AbstractString, sch::Tables.Schema; debug::Bool=false, quoteidentifiers::Bool=true, createtableclause::AbstractString="CREATE TABLE", columnsuffix=Dict(), auto_increment_primary_key_name::AbstractString="")
38+
function createtable(conn::Connection, nm::AbstractString, sch::Tables.Schema; debug::Bool=false, quoteidentifiers::Bool=true, createtableclause::AbstractString="CREATE TABLE", coltypes=Dict(), columnsuffix=Dict(), auto_increment_primary_key_name::Union{Nothing,AbstractString}=nothing
3839
names = sch.names
3940
checkdupnames(names)
40-
types = [sqltype(T) for T in sch.types]
41+
types = [sqltype(T, coltypes, names[i]) for (i, T) in enumerate(sch.types)]
4142
columns = (string(quoteidentifiers ? quoteid(String(names[i])) : names[i], ' ', types[i], ' ', get(columnsuffix, names[i], "")) for i = 1:length(names))
42-
auto_increment_column = isempty(auto_increment_primary_key_name) ? "" : "$(auto_increment_primary_key_name) INT AUTO_INCREMENT PRIMARY KEY, "
43+
auto_increment_column = (auto_increment_primary_key_name === nothing || isempty(auto_increment_primary_key_name)) ? "" : "$(auto_increment_primary_key_name) INT AUTO_INCREMENT PRIMARY KEY, "
4344
debug && @info "executing create table statement: `$createtableclause $nm ($(auto_increment_column)$(join(columns, ", ")))`"
4445
return DBInterface.execute(conn, "$createtableclause $nm ($(auto_increment_column)$(join(columns, ", ")))")
4546
end
4647

4748
"""
48-
MySQL.load(table, conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, columnsuffix=Dict(), debug=false)
49-
table |> MySQL.load(conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, columnsuffix=Dict(), debug=false)
49+
MySQL.load(table, conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, coltypes=Dict(), columnsuffix=Dict(), debug=false)
50+
table |> MySQL.load(conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, coltypes=Dict(), columnsuffix=Dict(), debug=false)
5051
5152
Attempts to take a Tables.jl source `table` and load into the database represented by `conn` with table name `name`.
5253
5354
It first detects the `Tables.Schema` of the table source and generates a `CREATE TABLE` statement
54-
with the appropriate column names and types. If no table name is provided, one will be autogenerated, like `odbcjl_xxxxx`.
55+
with the appropriate column names and types. If no table name is provided, one will be autogenerated, like `mysql_xxxxx`.
5556
The `CREATE TABLE` clause can be provided manually by passing the `createtableclause` keyword argument, which
5657
would allow specifying a temporary table or `if not exists`.
57-
Column definitions can also be enhanced by providing arguments to `columnsuffix` as a `Dict` of
58+
Column types can be overridden by providing the `coltypes` keyword argument as a `Dict` of
59+
column name (given as a `Symbol`) to a string of the SQL type. This allows, for example, using
60+
a `LONGBLOB` instead of `BLOB` for large binary data by doing `coltypes=Dict(:Photo => "LONGBLOB")`.
61+
Column definitions can also be enhanced by providing arguments to `columnsuffix` as a `Dict` of
5862
column name (given as a `Symbol`) to a string of the enhancement that will come after name and type like
5963
`[column name] [column type] enhancements`. This allows, for example, specifying the charset of a string column
6064
by doing something like `columnsuffix=Dict(:Name => "CHARACTER SET utf8mb4")`.

test/runtests.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ ct0 = (x = UInt8[1, 22, 23],)
161161
MySQL.load(ct0,conn, debug=true)
162162
#there is no @test invocation here; but the above call failed due to Random not being imported
163163

164+
# https://github.com/JuliaDatabases/MySQL.jl/issues/225
165+
# Test that coltypes parameter allows overriding default SQL types (e.g., BLOB -> LONGBLOB)
166+
ct225 = (
167+
id = Int32[1, 2],
168+
data = [UInt8[1, 2, 3], UInt8[4, 5, 6]],
169+
)
170+
MySQL.load(ct225, conn, "test225"; coltypes=Dict(:data => "LONGBLOB"), debug=true)
171+
col_info = DBInterface.execute(conn, "SHOW COLUMNS FROM test225 WHERE Field = 'data'") |> columntable
172+
# Type column may be returned as Vector{UInt8} or String depending on MySQL version
173+
col_type = col_info.Type[1]
174+
col_type_str = col_type isa Vector{UInt8} ? String(col_type) : col_type
175+
@test lowercase(col_type_str) == "longblob"
176+
# Also verify data roundtrips correctly
177+
ct225_roundtrip = DBInterface.execute(conn, "SELECT * FROM test225") |> columntable
178+
@test ct225_roundtrip.data == ct225.data
179+
164180
# now test insert/parameter binding
165181
DBInterface.execute(conn, "DELETE FROM Employee")
166182
for i = 1:length(expected)

0 commit comments

Comments
 (0)