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
34 changes: 31 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
# SQLite and DuckDB only, the others are skipped.
#
# check pull requests and master: one Linux build with all tests, the check of merging
# platforms master and manual runs: Windows, macOS and Linux build, start the runnable jar and take screenshots,
# it does not hold up pull requests
# platforms master, tags and manual runs: Windows, macOS and Linux build, start the runnable jar and take
# screenshots, packages for download (src/packaging/package.sh); it does not hold up pull requests
# release tags v*: a GitHub release with the packages of every platform
name: build

on:
push:
branches: [master]
tags: ['v*']
paths-ignore: ['docs/**', '**/*.md', 'change_logs/**']
pull_request:
paths-ignore: ['docs/**', '**/*.md', 'change_logs/**']
Expand Down Expand Up @@ -45,6 +47,9 @@ jobs:
platform: win
- os: macos-latest
platform: mac-aarch64
# Intel Macs, jpackage builds for the architecture it runs on
- os: macos-15-intel
platform: mac
- os: ubuntu-latest
platform: linux
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -97,11 +102,34 @@ jobs:
grep -q ":: SqlJam ::" run.log
if grep -E "Exception|ERROR" run.log; then exit 1; fi

- name: Package for download
shell: bash
run: sh src/packaging/package.sh ${{ matrix.platform }}

- uses: actions/upload-artifact@v4
if: always()
with:
name: sqljam-${{ matrix.platform }}
path: |
screenshots/
run.log
bin/sqljam-*-${{ matrix.platform }}.jar
bin/SqlJam-*
bin/sqljam_*.deb

release:
if: startsWith(github.ref, 'refs/tags/v')
needs: platforms
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts

- name: Create the release with the packages
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --title "SqlJam $GITHUB_REF_NAME" \
--generate-notes $(find artifacts -name 'SqlJam-*' -o -name 'sqljam_*.deb')
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ logs/

# Other
change_logs

# Local notes, not published
docs/release-guide.md
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div align="center">

<img src="docs/assets/logo.png" alt="SqlJam logo" width="240">

# SqlJam

**Move tables across databases. Schema, data and all.**
Expand Down
Binary file modified docs/assets/default_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/import-package.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/import-parquet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/main-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/main-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/main-nord.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/package-viewer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/sql-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/sqljam-tutorial.mp4
Binary file not shown.
Binary file modified docs/assets/table-data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/blogger/sqljam-en.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SqlJam: Move Tables Across Databases with Schema, Data and All

<p align="center"><img src="https://raw.githubusercontent.com/paganini2008/sqljam/master/docs/assets/logo.png" alt="SqlJam logo" width="240"></p>

> Tables, indexes, constraints, sequences, partitions and rows, in one go.

## 1. Overview
Expand Down
2 changes: 2 additions & 0 deletions docs/blogger/sqljam-medium.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SqlJam: Move Tables Across Databases with Schema, Data and All

![SqlJam logo](https://raw.githubusercontent.com/paganini2008/sqljam/master/docs/assets/logo.png)

Tables, indexes, constraints, sequences, partitions and rows, in one go.

![SqlJam main window](https://raw.githubusercontent.com/paganini2008/sqljam/master/docs/assets/main-dark.png)
Expand Down
2 changes: 2 additions & 0 deletions docs/blogger/sqljam-zh.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SqlJam:跨数据库,一键搬表

<p align="center"><img src="https://raw.githubusercontent.com/paganini2008/sqljam/master/docs/assets/logo.png" alt="SqlJam logo" width="240"></p>

> 结构、索引、约束、序列、分区、数据,一起搬走。

## 1. Overview
Expand Down
8 changes: 5 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<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.">
<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>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
Expand Down Expand Up @@ -69,8 +70,8 @@
nav .wrap { display: flex; align-items: center; justify-content: space-between; height: 60px; gap: 16px; }
.brand { display: flex; align-items: center; gap: 10px; color: var(--text); font-weight: 800; font-size: 18px; }
.brand:hover { text-decoration: none; }
.logo { width: 30px; height: 30px; border-radius: 8px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); display: grid; place-items: center; }
.logo svg { width: 18px; height: 18px; }
.logo { width: 34px; height: 34px; object-fit: contain; }
.hero-logo { width: 240px; max-width: 60%; height: auto; margin: 0 auto 8px; display: block; }
.links { display: flex; gap: 22px; font-size: 14px; }
.links a { color: var(--muted); }
.links a:hover { color: var(--text); text-decoration: none; }
Expand Down Expand Up @@ -169,7 +170,7 @@
<nav aria-label="Main">
<div class="wrap">
<a class="brand" href="#">
<span class="logo" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg></span>
<img class="logo" src="assets/favicon.png" alt="" aria-hidden="true">
SqlJam
</a>
<div class="links">
Expand All @@ -183,6 +184,7 @@
<!-- 1. Hero -->
<header class="hero">
<div class="wrap">
<img class="hero-logo" src="assets/logo.png" alt="SqlJam logo" width="240" height="182">
<h1><span>SqlJam</span></h1>
<p class="slogan">Move tables across databases. Schema, data and all.</p>
<p class="lead">A desktop import/export tool that copies tables, indexes, constraints, sequences, partitions and rows between relational and OLAP databases, directly, as copies in the same schema, or through a self-describing SQL, Parquet or CSV export package you can import later. Imports into a database run in one transaction which an error rolls back, and rows are inserted, updated or skipped by key.</p>
Expand Down
34 changes: 34 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -652,5 +652,39 @@
</plugins>
</build>
</profile>
<profile>
<!-- Packages for download of this system (jpackage), see src/packaging/package.sh:
mvn package -Papp -DskipTests -Dapp.platform=mac-aarch64 (mac, win, linux) -->
<id>app</id>
<properties>
<app.platform>mac-aarch64</app.platform>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>app</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<exec executable="sh" dir="${project.basedir}" failonerror="true">
<arg value="src/packaging/package.sh"/>
<arg value="${app.platform}"/>
<arg value="${project.version}"/>
</exec>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Binary file modified src/bin/sqljam.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
141 changes: 141 additions & 0 deletions src/main/java/com/github/sqljam/face/service/JdbcUrls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2023-2026 Fred Feng
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.sqljam.face.service;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import com.github.sqljam.face.model.ConnectionProfile;
import com.github.sqljam.impexp.DbType;

/**
* @Description: JdbcUrls reads the database type, host, port and database of a jdbc url, and builds the url of them,
* so that the fields of a data source and its url follow each other like DBeaver does
* @Author: Fred Feng
* @Date: 18/10/2026
* @Version 1.0.0
*/
public final class JdbcUrls {

/**
* jdbc:mysql://host:port/db?..., jdbc:mariadb, jdbc:postgresql, jdbc:db2 (db:props;) and ClickHouse alike
*/
private static final Pattern SERVER = Pattern.compile(
"(?i)^jdbc:(?:mysql|mariadb|postgresql|db2|clickhouse|ch):(?:https?:)?//([^/:?;\\\\]+)(?::(\\d+))?"
+ "(?:/([^?;:]*))?.*$");
/**
* jdbc:oracle:thin:@//host:port/service, @host:port/service and @host:port:SID
*/
private static final Pattern ORACLE = Pattern.compile(
"(?i)^jdbc:oracle:thin:@(?://)?([^/:]+)(?::(\\d+))?[/:]([^?]+?)(?:\\?.*)?$");
/**
* jdbc:sqlserver://host\instance:port;databaseName=db;...
*/
private static final Pattern SQLSERVER = Pattern.compile(
"(?i)^jdbc:sqlserver://([^;:\\\\]+)(?:\\\\[^;:]+)?(?::(\\d+))?(;.*)?$");
private static final Pattern SQLSERVER_DATABASE = Pattern.compile("(?i);(?:databaseName|database)=([^;]+)");
private static final Pattern H2_SERVER = Pattern.compile("(?i)^jdbc:h2:(?:tcp|ssl)://([^/:]+)(?::(\\d+))?/(.+)$");

private JdbcUrls() {
}

/**
* Database type, host, port and database of the url, null if it is not a url of a supported database
*/
public static ConnectionProfile parse(String url) {
DbType dbType = DbType.forUrl(StringUtils.trim(url));
if (dbType == null) {
return null;
}
String text = url.trim();
ConnectionProfile profile = new ConnectionProfile();
profile.setDbType(dbType);
profile.setPort(dbType.getDefaultPort());
Matcher matcher;
switch (dbType) {
case ORACLE:
matcher = ORACLE.matcher(text);
// A TNS descriptor, e.g. @(DESCRIPTION=...), is kept as the url only
if (!matcher.matches()) {
return profile;
}
return fill(profile, matcher.group(1), matcher.group(2), matcher.group(3));
case SQLSERVER:
matcher = SQLSERVER.matcher(text);
if (!matcher.matches()) {
return profile;
}
Matcher database = SQLSERVER_DATABASE.matcher(StringUtils.defaultString(matcher.group(3)));
return fill(profile, matcher.group(1), matcher.group(2), database.find() ? database.group(1) : null);
case H2:
matcher = H2_SERVER.matcher(text);
if (matcher.matches()) {
return fill(profile, matcher.group(1), matcher.group(2), matcher.group(3));
}
// jdbc:h2:file:path, jdbc:h2:path and jdbc:h2:mem:name, options follow a semicolon
String path = StringUtils.substringBefore(text.substring("jdbc:h2:".length()), ";");
if (path.toLowerCase(Locale.ENGLISH).startsWith("file:")) {
path = path.substring("file:".length());
}
return fill(profile, null, null, path);
case SQLITE:
return fill(profile, null, null, StringUtils.substringBefore(text.substring("jdbc:sqlite:".length()),
"?"));
case DUCKDB:
return fill(profile, null, null, StringUtils.substringBefore(text.substring("jdbc:duckdb:".length()),
"?"));
default:
matcher = SERVER.matcher(text);
if (!matcher.matches()) {
return profile;
}
return fill(profile, matcher.group(1), matcher.group(2), matcher.group(3));
}
}

private static ConnectionProfile fill(ConnectionProfile profile, String host, String port, String database) {
profile.setHostname(StringUtils.trimToNull(host));
if (StringUtils.isNotBlank(port)) {
profile.setPort(Integer.parseInt(port));
} else if (profile.getHostname() == null) {
// Files have no port
profile.setPort(0);
}
profile.setDatabase(StringUtils.trimToNull(database));
return profile;
}

/**
* Url of the fields, while they are being entered: a missing host or a required database is left empty
*/
public static String build(DbType dbType, String host, int port, String database) {
if (dbType == null) {
return "";
}
String hostname = StringUtils.trimToNull(host);
boolean file = dbType.isFileBased() && hostname == null;
if (file && StringUtils.isBlank(database)) {
return "";
}
// A placeholder passes the checks of required databases, it is removed from the url
String placeholder = "\u0000";
String url = dbType.getUrl(file ? null : StringUtils.defaultString(hostname), port,
StringUtils.isBlank(database) ? placeholder : database.trim());
return url.replace(placeholder, "");
}
}
Loading
Loading