diff --git a/docs/src/index.md b/docs/src/index.md index b13604b..eaa8715 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,32 +1,76 @@ # ODBC.jl -The `ODBC.jl` package provides high-level julia functionality over the low-level ODBC API. In particular, the package allows making connections with any database that has a valid ODBC driver, sending SQL queries to those databases, and streaming the results into a variety of data sinks. - ```@contents Depth = 3 ``` -## ODBC administrative functions -```@docs -ODBC.drivers -ODBC.dsns -ODBC.adddriver -ODBC.removedriver -ODBC.adddsn -ODBC.removedsn -ODBC.setdebug +The ODBC.jl package provides a Julia interface for the ODBC API as implemented by various ODBC driver managers. More specifically, it provides a prebuilt copy of iODBC and unixODBC for OSX/Linux platforms, while still relying on the system-provided libraries on Windows. This means that no extra installation of a driver manager is necessary after installing the ODBC.jl package like: + +```julia +] add ODBC ``` -## DBMS Connections +Another common source of headache with ODBC is the various locations of configuration files on OSX/Linux; to remedy this, ODBC.jl writes and loads its own `odbc.ini` and `odbcinst.ini` configuration files inside the package directory, like `ODBC/config/odbc.ini`. This ensures ODBC enviornment variables like `ODBCINI` are correctly set to the ODBC.jl managed config files. Additionally, ODBC.jl provides convenient ODBC administrative functions to add/remove drivers and dsns (see [`ODBC.addriver`](@ref) and [`ODBC.adddsn`](@ref)). + +What this all means is that hopefully ODBC.jl provides the easiest setup experience possible for a slightly dated API that is known for configuration complexities. + +## Getting Started + +Once ODBC.jl is installed, you'll want, at a minimum, to configure ODBC drivers for the specific databases you'll be connecting to. A reminder on ODBC architecture that each database must build/distribute their own compliant ODBC driver that can talk with the ODBC.jl-provided driver manager to make connections, execute queries, etc. What's more, individual database drivers must often build against a specific driver manager (or specific driver manager per platform). By default, ODBC.jl will use iODBC as driver manager on OSX, unixODBC on Linux platforms, and the system-provided driver manager on Windows. If a database driver mentions a requirement for a specific driver manager, ODBC.jl provides a way to switch between them, even at run-time (see [`ODBC.setiODBC`](@ref) and [`ODBC.setunixODBC`](@ref)). + +To install an ODBC driver, you can call: +```julia +ODBC.adddriver("name of driver", "full, absolute path to driver shared library"; kw...) +``` +passing the name of the driver, the full, absolute path to the driver shared library, and any additional keyword arguments which will be included as `KEY=VALUE` pairs in the `.ini` config files. ***NOTE*** on Windows, you likely need to start Julia (or your terminal) with administrative privileges (like ctrl + right-click the application, then choose open as admin) in order to add drivers via ODBC like this. + +### Connections + +Once a driver or two are installed (viewable by calling `ODBC.drivers()`), you can either: + * Setup a DSN, via `ODBC.adddsn("dsn name", "driver name"; kw...)` + * Make a connection directly by using a full connection string like `ODBC.Connection(connection_string)` + +In setting up a DSN, you can specify all the configuration options once, then connect by just calling `ODBC.Connection("dsn name")`, optionally passing a username and password as the 2nd and 3rd arguments. Alternatively, crafting and connecting via a fully specified connection string can mean less config-file dependency. + +### Executing Queries + +To execute queries, there are two paths: + * `DBInterface.execute(conn, sql, params)`: directly execute a SQL query and return a `Cursor` for any resultset + * `stmt = DBInterface.prepare(conn, sql); DBInterface.execute(stmt, params)`: first prepare a SQL statement, then execute, perhaps multiple times with different parameters +Both forms of `DBInterface.execute` return a `Cursor` object that satisfies the [Tables.jl](https://juliadata.github.io/Tables.jl/stable/), so results can be utilized in whichever way is most convenient, like `DataFrame(x)`, `CSV.write("results.csv", x)` or materialzed as a plain `Matrix` (`Tables.matrix(x)`), `NamedTuple` (`Tables.columntable(x)`), or `Vector` of `NamedTuple` (`Tables.rowtable(x)`). + +### Loading data + +ODBC.jl attempts to provide a convenient `ODBC.load(table, conn, table_name)` function for generically loading Tables.jl-compatible sources into database tables. While the ODBC spec has some utilities for even making this possible, just note that it can be tricky to do generically in practice due to differences in database requirements for `CREATE TABLE` and column type statements. + +## API Reference + +### DBMS Connections ```@docs DBInterface.connect ODBC.Connection DBInterface.close! ``` -## Query execution and result handling +### Query execution and result handling ```@docs DBInterface.prepare DBInterface.execute DBInterface.executemultiple ``` + +### Data loading +```@docs +ODBC.load +``` + +### ODBC administrative functions +```@docs +ODBC.drivers +ODBC.dsns +ODBC.adddriver +ODBC.removedriver +ODBC.adddsn +ODBC.removedsn +ODBC.setdebug +``` diff --git a/src/load.jl b/src/load.jl index 745ed92..8f178c6 100644 --- a/src/load.jl +++ b/src/load.jl @@ -46,9 +46,32 @@ function createtable(conn::Connection, nm::AbstractString, sch::Tables.Schema; d return DBInterface.execute(conn, "$createtableclause $nm ($(join(columns, ", ")))") end +""" + ODBC.load(table, conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, columnsuffix=Dict(), debug=false) + table |> ODBC.load(conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), createtableclause=nothing, columnsuffix=Dict(), debug=false) + +Attempts to take a Tables.jl source `table` and load into the database represented by `conn` with table name `name`. + +It first detects the `Tables.Schema` of the table source and generates a `CREATE TABLE` statement +with the appropriate column names and types. If no table name is provided, one will be autogenerated, like `odbcjl_xxxxx`. +The `CREATE TABLE` clause can be provided manually by passing the `createtableclause` keyword argument, which +would allow specifying a temporary table or `if not exists`. +Column definitions can also be enhanced by providing arguments to `columnsuffix` as a `Dict` of +column name (given as a `Symbol`) to a string of the enhancement that will come after name and type like +`[column name] [column type] enhancements`. This allows, for example, specifying the charset of a string column +by doing something like `columnsuffix=Dict(:Name => "CHARACTER SET utf8mb4")`. + +Do note that databases vary wildly in requirements for `CREATE TABLE` and column definitions +so it can be extremely difficult to load data generically. You may just need to tweak some of the provided +keyword arguments, but you may also need to execute the `CREATE TABLE` and `INSERT` statements +yourself. If you run into issues, you can [open an issue](https://github.com/JuliaDatabases/ODBC.jl/issues) and +we can see if there's something we can do to make it easier to use this function. +""" +function load end + load(conn::Connection, table::AbstractString="odbcjl_"*Random.randstring(5); kw...) = x->load(x, conn, table; kw...) -function load(itr, conn::Connection, name::AbstractString="odbcjl_"*Random.randstring(5); append::Bool=true, quoteidentifiers::Bool=true, debug::Bool=true, limit::Integer=typemax(Int64), kw...) +function load(itr, conn::Connection, name::AbstractString="odbcjl_"*Random.randstring(5); append::Bool=true, quoteidentifiers::Bool=true, debug::Bool=false, limit::Integer=typemax(Int64), kw...) # get data rows = Tables.rows(itr) sch = Tables.schema(rows)