Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Move tables across databases. Schema, data and all.**

SqlJam is a desktop import/export tool for relational and OLAP databases: MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, DuckDB, ClickHouse and more.
SqlJam is a desktop import/export tool for relational and OLAP databases: MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, Microsoft Access, DuckDB, ClickHouse and more.
It copies tables (with indexes, constraints, sequences and partitions) **directly into another database** or into the same schema as copies, or saves them as a **self-describing export package** of SQL, **Parquet** or **CSV** files that can be imported later. Imports into a database run in **one transaction** which an error rolls back as a whole, and rows are **inserted, updated or skipped by key**.

![Java](https://img.shields.io/badge/Java-17-ED8B00?logo=openjdk&logoColor=white)
Expand Down Expand Up @@ -101,6 +101,7 @@ Only **Java 17+** is needed to run SqlJam. JavaFX and all JDBC drivers are insid
| MariaDB | 11.8 | 10.3 |
| H2 | 2.3 | 2.x |
| SQLite | 3.46 | 3.x |
| Microsoft Access (`.accdb`, `.mdb`) | 2016 | 2000 |
| DuckDB (OLAP) | 1.5 | 1.x |
| ClickHouse (OLAP) | 26.9 | 24.x |

Expand Down Expand Up @@ -309,6 +310,7 @@ try (ManualTransaction transaction = new ManualTransaction(connection, DbType.PO
| MySQL / Oracle `VECTOR` | `text` | same | `nvarchar(max)` | same |
| ClickHouse `Nullable(String)` / `Array(T)` | `text` | `CLOB` | `nvarchar(max)` | `longtext` |
| DuckDB `HUGEINT` / `UNION` | `numeric(38, 0)` / `text` | `NUMBER(38,0)` / `CLOB` | `decimal(38,0)` / `nvarchar(max)` | `decimal(38,0)` / `longtext` |
| Access `CURRENCY` / `YESNO` / `OLE` | `numeric(19, 4)` / `boolean` / `bytea` | `NUMBER(19,4)` / `NUMBER(1)` / `BLOB` | `decimal(19,4)` / `bit` / `varbinary(max)` | `decimal(19,4)` / `tinyint(1)` / `longblob` |

### Best practices

Expand Down Expand Up @@ -438,7 +440,7 @@ Environment: Apple M2 Max, 32 GB, JDK 17. MySQL/PostgreSQL local, Oracle/SQL Ser
| Why does Db2 refuse a wide table? | Rows wider than a 4K page need a table space of larger pages. Create one once, e.g. `CREATE BUFFERPOOL BP32K PAGESIZE 32K` and `CREATE TABLESPACE TS32K PAGESIZE 32K BUFFERPOOL BP32K`, Db2 then places wide tables there by itself |
| Do names have to match in case? | No. An existing target table and its columns are matched without case, e.g. `OrderId` of the source writes the column `orderid` of PostgreSQL or `ORDERID` of Oracle |
| Which rows were skipped? | Each skipped row is in the log of the import with its key and its error. **Errors only** shows them, **Save Log** writes the whole log to a file |
| What does a roll back undo? | Everything on PostgreSQL, SQL Server, Db2, SQLite and DuckDB, tables included. MySQL, MariaDB, Oracle and H2 commit DDL at once, there the created tables stay empty and keys and identity values are set after the commit |
| What does a roll back undo? | Everything on PostgreSQL, SQL Server, Db2, SQLite and DuckDB, tables included. MySQL, MariaDB, Oracle, H2 and Access commit DDL at once, there the created tables stay empty and keys and identity values are set after the commit |
| Can I export only the structure? | Yes. Rows are optional. Pick content **Structure** (`ExportMode.DDL`) for a package or a direct import. Tables, keys, indexes, comments, sequences and partitions are created without rows |
| Are partitioned tables kept? | Yes. The same database type creates the partitioning again with its partitions, bounds and rows: range, list, hash, key and subpartitions of MySQL and MariaDB (system time too), range, list, hash, default and nested partitions of PostgreSQL, range, list, hash, interval, automatic list, composite and reference partitions of Oracle, partition functions of SQL Server, range partitions of Db2 and partition keys of ClickHouse. Other databases get the tables with all rows. In the tree a partitioned table shows its partitioning next to its name and PostgreSQL partitions are listed under their table |
| Can I generate scripts for an older server? | Yes, choose the target version (e.g. Oracle `11.2`, SQL Server `2008`) and a compatible dialect is used |
Expand Down
3 changes: 2 additions & 1 deletion docs/blogger/sqljam-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

**SqlJam**: *Move tables across databases. Schema, data and all.*

A Java 17 + JavaFX desktop tool that copies tables between relational and OLAP databases (**MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, DuckDB, ClickHouse** and more) in any direction:
A Java 17 + JavaFX desktop tool that copies tables between relational and OLAP databases (**MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, Microsoft Access, DuckDB, ClickHouse** and more) in any direction:
**import directly** into a target database or into the same schema as copies, or save a self-describing **export package** of SQL, **Parquet** or **CSV** files and import it later. Imports into a database run in **one transaction** which an error rolls back, and rows are **inserted, updated or skipped by key**.

![SqlJam main window](https://raw.githubusercontent.com/paganini2008/sqljam/master/docs/assets/main-dark.png)
Expand Down Expand Up @@ -101,6 +101,7 @@ Only **Java 17+** is needed to run SqlJam. JavaFX and the JDBC drivers are insid
| SQL Server | 2008 to 2022 |
| MariaDB | 10.3 to 11.x |
| H2 / SQLite | 2.x / 3.x |
| Microsoft Access (.accdb, .mdb) | 2000 to 2019 |
| DuckDB (OLAP) | 1.x |
| ClickHouse (OLAP) | 24.x to 26.x |

Expand Down
3 changes: 2 additions & 1 deletion docs/blogger/sqljam-medium.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Tables, indexes, constraints, sequences, partitions and rows, in one go.

## Overview

**SqlJam** is a Java 17 + JavaFX desktop tool that copies tables between relational and OLAP databases (**MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, DuckDB, ClickHouse** and more) in any direction. Import directly into a target database or into the same schema as copies, or save a self-describing **export package** of SQL, **Parquet** or **CSV** files and import it later. Imports into a database run in **one transaction** which an error rolls back, and rows are **inserted, updated or skipped by key**.
**SqlJam** is a Java 17 + JavaFX desktop tool that copies tables between relational and OLAP databases (**MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, Microsoft Access, DuckDB, ClickHouse** and more) in any direction. Import directly into a target database or into the same schema as copies, or save a self-describing **export package** of SQL, **Parquet** or **CSV** files and import it later. Imports into a database run in **one transaction** which an error rolls back, and rows are **inserted, updated or skipped by key**.

Source: https://github.com/paganini2008/sqljam

Expand Down Expand Up @@ -97,6 +97,7 @@ Databases:
- SQL Server 2008 to 2022
- MariaDB 10.3 to 11.x
- H2 2.x, SQLite 3.x
- Microsoft Access 2000 to 2019 (.accdb and .mdb files)
- DuckDB 1.x and ClickHouse 24.x to 26.x (OLAP)
- All JDBC drivers are bundled

Expand Down
3 changes: 2 additions & 1 deletion docs/blogger/sqljam-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

**SqlJam**:*Move tables across databases. Schema, data and all.*

一个 Java 17 + JavaFX 的桌面导入导出工具,支持关系型与 OLAP 数据库(**MySQL、MariaDB、PostgreSQL、Oracle、SQL Server、Db2、H2、SQLite、DuckDB、ClickHouse** 等)任意互导:
一个 Java 17 + JavaFX 的桌面导入导出工具,支持关系型与 OLAP 数据库(**MySQL、MariaDB、PostgreSQL、Oracle、SQL Server、Db2、H2、SQLite、Microsoft Access、DuckDB、ClickHouse** 等)任意互导:
可以**直接导入目标库**,也可以在同一个 schema 里复制表,或者导出为带描述文件的 **SQL / Parquet / CSV 导出包**,之后再成对导入。导入目标库默认在**一个事务**里完成,出错整体回滚,数据可以**按主键更新、插入或跳过**。

![SqlJam 主界面](https://raw.githubusercontent.com/paganini2008/sqljam/master/docs/assets/main-dark.png)
Expand Down Expand Up @@ -101,6 +101,7 @@ bin/
| SQL Server | 2008 ~ 2022 |
| MariaDB | 10.3 ~ 11.x |
| H2 / SQLite | 2.x / 3.x |
| Microsoft Access(.accdb、.mdb) | 2000 ~ 2019 |
| DuckDB(OLAP) | 1.x |
| ClickHouse(OLAP) | 24.x ~ 26.x |

Expand Down
6 changes: 4 additions & 2 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ mvn -DskipTests install
</dependency>
```

The JDBC drivers of MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, DuckDB and ClickHouse come with it.
The JDBC drivers of MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, Microsoft Access (UCanAccess), DuckDB and ClickHouse come with it.

---

Expand Down Expand Up @@ -331,7 +331,7 @@ The key is the primary key of the table, or the chosen columns: columns of a uni
|---|---|---|
| PostgreSQL, SQLite, DuckDB | `ON CONFLICT … DO UPDATE` | `ON CONFLICT … DO NOTHING` |
| MySQL, MariaDB | `ON DUPLICATE KEY UPDATE` | `ON DUPLICATE KEY UPDATE` without changes |
| Oracle, SQL Server, Db2 | `MERGE … WHEN MATCHED … WHEN NOT MATCHED` | `MERGE … WHEN NOT MATCHED` |
| Oracle, SQL Server, Db2, Access | `MERGE … WHEN MATCHED … WHEN NOT MATCHED` | `MERGE … WHEN NOT MATCHED` |
| H2 | `MERGE INTO … KEY` | `MERGE … WHEN NOT MATCHED` |

Names of an existing target table are matched without case, `OrderId` of the source writes `orderid` of PostgreSQL or `ORDERID` of Oracle.
Expand Down Expand Up @@ -426,6 +426,8 @@ copier.setExportListener(new ExportListener() {
| Dialect: types, literals, DDL, paging, upserts | `impexp.db.XxxDialect extends Dialect` |
| Metadata: catalogs, schemas, tables, columns, keys | `impexp.db.XxxMetaDataOperations extends MetaDataOperations` |
| Older versions | `XxxNNDialect extends XxxDialect`, chosen by `Dialect.forVersion` |
| Versions other than the server version, e.g. the file format of Access | `Dialect.readDatabaseVersion` |
| Tables which are emptied before they are dropped, drops of missing tables | `Dialect.getEmptyTableStatement`, `Dialect.isMissingDropError` |
| Registration | A constant of `DbType` with driver, URL template and category |
| Fixture and tests | `src/test/resources/<db>/fixture.sql`, a value of `ItDatabase`, the cross-database matrix runs it |

Expand Down
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SqlJam · Move tables across databases</title>
<meta name="description" content="SqlJam copies tables, indexes, constraints, sequences, partitions and rows between MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, DuckDB, ClickHouse and more, directly or through a self-describing SQL, Parquet or CSV export package, imports committed or rolled back as a whole.">
<meta name="description" content="SqlJam copies tables, indexes, constraints, sequences, partitions and rows between MySQL, MariaDB, PostgreSQL, Oracle, SQL Server, Db2, H2, SQLite, Microsoft Access, DuckDB, ClickHouse and more, directly or through a self-describing SQL, Parquet or CSV export package, imports committed or rolled back as a whole.">
<link rel="icon" type="image/png" href="assets/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
Expand Down Expand Up @@ -191,7 +191,7 @@ <h1><span>SqlJam</span></h1>
<div class="db-chips">
<span class="chip">MySQL</span><span class="chip">MariaDB</span><span class="chip">PostgreSQL</span><span class="chip">Oracle</span>
<span class="chip">SQL Server</span><span class="chip">Db2</span><span class="chip">H2</span><span class="chip">SQLite</span>
<span class="chip">DuckDB</span><span class="chip">ClickHouse</span>
<span class="chip">Access</span><span class="chip">DuckDB</span><span class="chip">ClickHouse</span>
</div>
<div class="cta">
<a class="btn primary" href="#quick-start">Get Started</a>
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<richtextfx.version>0.11.7</richtextfx.version>
<db2.version>12.1.5.0</db2.version>
<duckdb.version>1.5.6.0</duckdb.version>
<ucanaccess.version>5.1.8</ucanaccess.version>
<clickhouse.version>0.10.0</clickhouse.version>
<lombok.version>1.18.48</lombok.version>
<main.class>com.github.sqljam.face.Launcher</main.class>
Expand Down Expand Up @@ -168,6 +169,12 @@
<artifactId>duckdb_jdbc</artifactId>
<version>${duckdb.version}</version>
</dependency>
<dependency>
<!-- Microsoft Access (.accdb, .mdb) in pure Java, no ODBC: UCanAccess on Jackcess -->
<groupId>io.github.spannm</groupId>
<artifactId>ucanaccess</artifactId>
<version>${ucanaccess.version}</version>
</dependency>
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,8 @@ public DataPage getData(String catalog, String schema, String table, TableQuery
totalRows = count != null ? count : 0;
}
try (Connection connection = getConnection(catalog)) {
Dialect dialect = this.dialect.forVersion(connection.getMetaData().getDatabaseMajorVersion(),
connection.getMetaData().getDatabaseMinorVersion());
int[] version = this.dialect.readDatabaseVersion(connection.getMetaData());
Dialect dialect = this.dialect.forVersion(version[0], version[1]);
List<String> primaryKeys = metaDataOperations.getPrimaryKeyInfos(connection.getMetaData(),
getMetaCatalog(catalog), schema, table).stream()
.sorted(Comparator.comparingInt(info -> TableMetaData.getInt(info, "KEY_SEQ")))
Expand Down Expand Up @@ -370,7 +370,7 @@ public DataPage getData(String catalog, String schema, String table, TableQuery
while (rs.next()) {
List<String> row = new ArrayList<>(indexes.size());
for (int i : indexes) {
row.add(formatValue(JdbcUtils.getColumnValue(rs, i, rsmd.getColumnType(i),
row.add(formatValue(dialect.getColumnValue(rs, i, rsmd.getColumnType(i),
rsmd.getColumnTypeName(i))));
}
rows.add(row);
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/github/sqljam/face/service/JdbcUrls.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ public static ConnectionProfile parse(String url) {
case DUCKDB:
return fill(profile, null, null, StringUtils.substringBefore(text.substring("jdbc:duckdb:".length()),
"?"));
case ACCESS:
// jdbc:ucanaccess://path;option=value
return fill(profile, null, null, StringUtils.substringBefore(StringUtils.removeStartIgnoreCase(
text.substring("jdbc:ucanaccess:".length()), "//"), ";"));
default:
matcher = SERVER.matcher(text);
if (!matcher.matches()) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/github/sqljam/face/view/ConnectionForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ private void browseFile() {
chooser.setInitialDirectory(current.getParentFile());
}
}
if (dbTypeCombo.getValue() == DbType.ACCESS) {
chooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter(DbType.ACCESS.getDisplayName(),
"*.accdb", "*.mdb"), new FileChooser.ExtensionFilter("*", "*.*"));
}
File file = chooser.showOpenDialog(getScene() != null ? getScene().getWindow() : null);
if (file != null) {
String path = file.getAbsolutePath();
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/github/sqljam/face/view/ExportDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public class ExportDialog extends Dialog<TransferRequest> {
VERSION_PRESETS.put(DbType.DB2, List.of("", "11.1", "11.5", "12.1"));
VERSION_PRESETS.put(DbType.H2, List.of("", "2.2"));
VERSION_PRESETS.put(DbType.SQLITE, List.of("", "3"));
// Access 2007, 2010 and 2016
VERSION_PRESETS.put(DbType.ACCESS, List.of("", "12", "14", "16"));
}

private final AppContext context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ protected void runScript(File file) throws IOException, SQLException {
if (existingSkipped) {
runner.setSkippedError(dbType.createDialect()::isAlreadyExistsError);
}
if (dbType != null) {
runner.setMissingDropError(dbType.createDialect()::isMissingDropError);
}
runner.setBatchSize(batchSize);
long fileBytes = file.length();
runner.setExportListener(new ForwardingListener(exportListener) {
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/com/github/sqljam/impexp/DbType.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
*/
package com.github.sqljam.impexp;

import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import com.github.sqljam.impexp.db.AccessDialect;
import com.github.sqljam.impexp.db.AccessMetaDataOperations;
import com.github.sqljam.impexp.db.ClickHouseDialect;
import com.github.sqljam.impexp.db.ClickHouseMetaDataOperations;
import com.github.sqljam.impexp.db.Db2Dialect;
Expand Down Expand Up @@ -229,6 +232,44 @@ public boolean isFileBased() {
}
},

ACCESS("Access", "net.ucanaccess.jdbc.UcanaccessDriver", 0, false, false, false, AccessDialect::new,
AccessMetaDataOperations::new) {
/**
* Database file of Microsoft Access (.accdb or .mdb), a missing file is created: .accdb in the format of
* Access 2016 (64-bit integers), .mdb in the format of Access 2002-2003
*/
@Override
public String getUrl(String hostname, int port, String catalogName) {
Validate.notBlank(catalogName, "Database file must be required while connecting to Access database.");
return normalizeUrl("jdbc:ucanaccess://" + catalogName);
}

@Override
public String normalizeUrl(String url) {
if (StringUtils.isBlank(url) || StringUtils.containsIgnoreCase(url, "newdatabaseversion=")) {
return url;
}
String file = StringUtils.substringBefore(url, ";");
return url + ";newdatabaseversion=" + (file.toLowerCase(Locale.ENGLISH).endsWith(".mdb") ? "V2003"
: "V2016");
}

@Override
public boolean isCatalogSupported() {
return false;
}

@Override
public boolean isFileBased() {
return true;
}

@Override
public String getValidationQuery() {
return "SELECT 1 FROM (VALUES (0))";
}
},

DUCKDB("DuckDB", "org.duckdb.DuckDBDriver", 0, false, true, true, DuckDBDialect::new,
DuckDBMetaDataOperations::new) {
@Override
Expand Down Expand Up @@ -451,6 +492,8 @@ public static DbType forUrl(String jdbcUrl) {
return H2;
} else if (url.startsWith("jdbc:sqlite:")) {
return SQLITE;
} else if (url.startsWith("jdbc:ucanaccess:")) {
return ACCESS;
} else if (url.startsWith("jdbc:duckdb:")) {
return DUCKDB;
} else if (url.startsWith("jdbc:clickhouse:") || url.startsWith("jdbc:ch:")) {
Expand Down
Loading
Loading