Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc93be5a9d | |||
| 7db2abdf18 | |||
| 6a925b83ca | |||
| c0f6e3a6e6 | |||
| ba24b874fc | |||
| 424d7d4ebb | |||
| 56fecff550 | |||
| 1ca507d1dd | |||
| 1717c7ee2c | |||
| 6c9b0f96a0 | |||
| 85355efe8f | |||
| 57093441c3 | |||
| f084f8380a | |||
| a9bd96b377 | |||
| c6d34847d5 | |||
| 4b8ffcdd1c | |||
| e9fc745d20 | |||
| 9cf698c67d | |||
| 20d40f069d | |||
| b0f104927c | |||
| 0ecb6860bd | |||
| c41ab99841 | |||
| 8cdd88d053 | |||
| 809f2a8949 | |||
| f47eaf4bac | |||
| 1392e0a6d8 | |||
| b0ee4c77d9 | |||
| b3a9151eff | |||
| e7b7d1bbba | |||
| 24fad6aa04 | |||
| b816399cba | |||
| fe39c6a2ae | |||
| c67c7b1360 | |||
| 05508d8b26 | |||
| 1050930667 | |||
| 509873e60b | |||
| 2245ef1ba2 | |||
| c7884f3605 | |||
| efd922b2e0 | |||
| 3b4af2bf47 | |||
| 0615163fad | |||
| cf5abeddbe | |||
| e60a92cfdc | |||
| 54ab5645b1 |
7
.gitignore
vendored
7
.gitignore
vendored
@ -5,3 +5,10 @@
|
|||||||
build
|
build
|
||||||
|
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
# Ignore local configuration files
|
||||||
|
run.yml
|
||||||
|
|
||||||
|
# Ignore accidentally extracted distribution files in project root
|
||||||
|
/bin/
|
||||||
|
/lib/
|
||||||
|
|||||||
158
CLAUDE.md
Normal file
158
CLAUDE.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
jrunner is a Java CLI tool for migrating data between databases. It reads data from a source database using SQL queries and writes it to a destination table, batching inserts for performance. The tool supports multiple database types via JDBC drivers including PostgreSQL, IBM AS/400, and Microsoft SQL Server.
|
||||||
|
|
||||||
|
## Build and Test Commands
|
||||||
|
|
||||||
|
Build the project:
|
||||||
|
```bash
|
||||||
|
gradle build
|
||||||
|
# or use wrapper
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
```bash
|
||||||
|
gradle test
|
||||||
|
# or use wrapper
|
||||||
|
./gradlew test
|
||||||
|
```
|
||||||
|
|
||||||
|
Build distribution package:
|
||||||
|
```bash
|
||||||
|
gradle build
|
||||||
|
# Creates jrunner/build/distributions/jrunner.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Local install for testing (recommended):
|
||||||
|
```bash
|
||||||
|
./gradlew installDist
|
||||||
|
# Creates executable at jrunner/build/install/jrunner/bin/jrunner
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy using interactive script:
|
||||||
|
```bash
|
||||||
|
./deploy.sh
|
||||||
|
# Choose: 1) Local install, 2) Global install to /opt, 3) Custom directory
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Single-File Design
|
||||||
|
The entire application logic resides in `jrunner/src/main/java/jrunner/jrunner.java`. This is a monolithic command-line tool with no abstraction layers or separate modules.
|
||||||
|
|
||||||
|
### Dual Mode Operation (v1.1+)
|
||||||
|
|
||||||
|
The tool operates in two modes:
|
||||||
|
|
||||||
|
**Query Mode** (new in v1.1):
|
||||||
|
- Activates automatically when destination flags are not provided
|
||||||
|
- Outputs query results to stdout in CSV or TSV format
|
||||||
|
- Silent operation - no diagnostic output, just clean data
|
||||||
|
- Designed for piping to visidata, pspg, less, or other data tools
|
||||||
|
- Format controlled by -f flag (csv or tsv)
|
||||||
|
|
||||||
|
**Migration Mode** (original functionality):
|
||||||
|
- Activates when destination flags are provided
|
||||||
|
- Reads from source, writes to destination with batched INSERTs
|
||||||
|
- Shows progress counters and timing information
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
**Query Mode:**
|
||||||
|
1. Parse command-line arguments (-scu, -scn, -scp for source)
|
||||||
|
2. Read SQL query from file specified by -sq flag
|
||||||
|
3. Connect to source database via JDBC
|
||||||
|
4. Execute source query and fetch results (fetch size: 10,000 rows)
|
||||||
|
5. Output results to stdout in CSV or TSV format
|
||||||
|
6. Close connection and exit
|
||||||
|
|
||||||
|
**Migration Mode:**
|
||||||
|
1. Parse command-line arguments (-scu, -scn, -scp for source; -dcu, -dcn, -dcp for destination)
|
||||||
|
2. Read SQL query from file specified by -sq flag
|
||||||
|
3. Connect to source and destination databases via JDBC
|
||||||
|
4. Execute source query and fetch results (fetch size: 10,000 rows)
|
||||||
|
5. Build batched INSERT statements (250 rows per batch)
|
||||||
|
6. Execute batches against destination table specified by -dt flag
|
||||||
|
7. Optionally clear target table before insert if -c flag is set
|
||||||
|
|
||||||
|
### Type Handling
|
||||||
|
The tool includes explicit handling for different SQL data types in a switch statement (lines 229-312). Supported types include VARCHAR, TEXT, CHAR, CLOB, DATE, TIME, TIMESTAMP, and BIGINT. String types get quote escaping and optional trimming.
|
||||||
|
|
||||||
|
### Database Drivers
|
||||||
|
JDBC drivers are configured in `jrunner/build.gradle`:
|
||||||
|
- PostgreSQL: org.postgresql:postgresql:42.5.0
|
||||||
|
- IBM AS/400 (JT400): net.sf.jt400:jt400:11.0
|
||||||
|
- Microsoft SQL Server: com.microsoft.sqlserver:mssql-jdbc:9.2.0.jre8
|
||||||
|
- SQL Server Integrated Auth: com.microsoft.sqlserver:mssql-jdbc_auth:9.2.0.x64
|
||||||
|
|
||||||
|
The AS/400 driver requires explicit Class.forName() registration (line 144).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The project uses a YAML configuration format (run.yml) to specify database connections, SQL script paths, and runtime options. However, the main application currently uses command-line arguments instead of parsing this YAML file.
|
||||||
|
|
||||||
|
Command-line flags:
|
||||||
|
- `-scu` - source JDBC URL
|
||||||
|
- `-scn` - source username
|
||||||
|
- `-scp` - source password
|
||||||
|
- `-dcu` - destination JDBC URL (migration mode only)
|
||||||
|
- `-dcn` - destination username (migration mode only)
|
||||||
|
- `-dcp` - destination password (migration mode only)
|
||||||
|
- `-sq` - path to source SQL query file
|
||||||
|
- `-dt` - fully qualified destination table name (migration mode only)
|
||||||
|
- `-t` - trim text fields (default: true)
|
||||||
|
- `-c` - clear target table before insert (default: true, migration mode only)
|
||||||
|
- `-f` - output format: csv, tsv (query mode only, default: csv)
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
### Mode Detection
|
||||||
|
Query mode is automatically detected at runtime (line 131) by checking if all destination flags (dcu, dcn, dcp, dt) are empty. This allows seamless switching between query and migration modes without explicit mode flags.
|
||||||
|
|
||||||
|
### Query Mode Output (v1.1+)
|
||||||
|
Query mode uses dedicated output methods:
|
||||||
|
- `outputQueryResults()` - Dispatches to format-specific methods
|
||||||
|
- `outputCSV()` - RFC 4180 compliant CSV with proper quote escaping
|
||||||
|
- `outputTSV()` - Tab-separated with tabs/newlines replaced by spaces
|
||||||
|
- All output goes to stdout; no diagnostic messages in query mode
|
||||||
|
- Helper methods: `escapeCSV()` and `escapeTSV()` for proper formatting
|
||||||
|
|
||||||
|
### Memory and Streaming Architecture
|
||||||
|
Both modes use a streaming architecture with no array storage of result rows:
|
||||||
|
|
||||||
|
**Query Mode Streaming:**
|
||||||
|
- Rows are pulled from the ResultSet via `rs.next()` one at a time
|
||||||
|
- Each row is immediately formatted and written to stdout
|
||||||
|
- No accumulation in memory - pure streaming from database to stdout
|
||||||
|
- The only buffer is the JDBC driver's internal fetch buffer (10,000 rows)
|
||||||
|
|
||||||
|
**Migration Mode Streaming:**
|
||||||
|
- Rows are pulled from the ResultSet via `rs.next()` one at a time
|
||||||
|
- Each row is converted to a SQL VALUES clause string: `(val1,val2,val3)`
|
||||||
|
- VALUES clauses are accumulated into a single `sql` string variable
|
||||||
|
- When 250 rows accumulate, the string is prepended with `INSERT INTO {table} VALUES` and executed
|
||||||
|
- The `sql` string is cleared and accumulation starts again
|
||||||
|
- Only holds up to 250 rows worth of SQL text in memory at once
|
||||||
|
|
||||||
|
**JDBC Fetch Size:**
|
||||||
|
- Both modes set `stmt.setFetchSize(10000)` (line 190)
|
||||||
|
- This is a hint to the JDBC driver to fetch 10,000 rows at a time from the database
|
||||||
|
- The driver maintains this internal buffer for network efficiency
|
||||||
|
- The application code never sees or stores all 10,000 rows - it processes them one at a time via `rs.next()`
|
||||||
|
|
||||||
|
### Batch Size (Migration Mode)
|
||||||
|
INSERT statements are batched at 250 rows (hardcoded around line 356). Rows are streamed into a SQL string buffer as VALUES clauses. When 250 rows accumulate in the string, it is prepended with "INSERT INTO {table} VALUES" and executed, then the string is cleared.
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
SQLException handling prints stack trace and exits immediately with System.exit(0). There is no transaction rollback or partial failure recovery.
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
- Result set fetch size is set to 10,000 rows (line 190)
|
||||||
|
- Progress counter prints with carriage return for real-time updates (migration mode only)
|
||||||
|
- Timestamps captured at start and end for duration tracking (migration mode only)
|
||||||
|
- Query mode has no progress output to keep stdout clean for piping
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Paul Trowbridge
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@ -1,296 +0,0 @@
|
|||||||
package jrunner;
|
|
||||||
import java.sql.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path ;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
public class jrunner {
|
|
||||||
//static final String QUERY = "SELECT * from rlarp.osm LIMIT 100";
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
|
|
||||||
String scu = "";
|
|
||||||
String scn = "";
|
|
||||||
String scp = "";
|
|
||||||
String dcu = "";
|
|
||||||
String dcn = "";
|
|
||||||
String dcp = "";
|
|
||||||
String sq = "";
|
|
||||||
String dt = "";
|
|
||||||
Boolean trim = true;
|
|
||||||
Integer r = 0;
|
|
||||||
Integer t = 0;
|
|
||||||
String sql = "";
|
|
||||||
String nr = "";
|
|
||||||
String nc = "";
|
|
||||||
String nl = "\n";
|
|
||||||
String msg = "";
|
|
||||||
Connection scon = null;
|
|
||||||
Connection dcon = null;
|
|
||||||
Statement stmt = null;
|
|
||||||
Statement stmtd = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
String[] getv = null;
|
|
||||||
Integer cols = null;
|
|
||||||
String[] dtn = null;
|
|
||||||
|
|
||||||
msg = "jrunner version 0.32";
|
|
||||||
msg = msg + nl + "-scu source jdbc url";
|
|
||||||
msg = msg + nl + "-scn source username";
|
|
||||||
msg = msg + nl + "-scp source passowrd";
|
|
||||||
msg = msg + nl + "-dcu destination jdbc url";
|
|
||||||
msg = msg + nl + "-dcn destination username";
|
|
||||||
msg = msg + nl + "-dcp destination passowrd";
|
|
||||||
msg = msg + nl + "-sq path to source query";
|
|
||||||
msg = msg + nl + "-dt fully qualified name of destination table";
|
|
||||||
msg = msg + nl + "-t trim text";
|
|
||||||
msg = msg + nl + "--help info";
|
|
||||||
|
|
||||||
//---------------------------------------parse args into variables-------------------------------------------------
|
|
||||||
|
|
||||||
for (int i = 0; i < args.length; i = i +1 ){
|
|
||||||
switch (args[i]) {
|
|
||||||
//source connection string
|
|
||||||
case "-scu":
|
|
||||||
scu = args[i+1];
|
|
||||||
break;
|
|
||||||
//source username
|
|
||||||
case "-scn":
|
|
||||||
scn = args[i+1];
|
|
||||||
break;
|
|
||||||
//source password
|
|
||||||
case "-scp":
|
|
||||||
scp = args[i+1];
|
|
||||||
break;
|
|
||||||
//destination connection string
|
|
||||||
case "-dcu":
|
|
||||||
dcu = args[i+1];
|
|
||||||
break;
|
|
||||||
//destination username
|
|
||||||
case "-dcn":
|
|
||||||
dcn = args[i+1];
|
|
||||||
break;
|
|
||||||
//destination password
|
|
||||||
case "-dcp":
|
|
||||||
dcp = args[i+1];
|
|
||||||
break;
|
|
||||||
//source query path
|
|
||||||
case "-sq":
|
|
||||||
try {
|
|
||||||
//sq = Files.readAllLines(Paths.get(args[i+1]));
|
|
||||||
sq = Files.readString(Paths.get(args[i+1]));
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
//System.out.println(nl + "error reasing source sql file: " + printStackTrace());
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
//destination table name
|
|
||||||
case "-dt":
|
|
||||||
dt = args[i+1];
|
|
||||||
break;
|
|
||||||
case "-t":
|
|
||||||
trim = true;
|
|
||||||
break;
|
|
||||||
case "-v":
|
|
||||||
System.out.println(msg);
|
|
||||||
return;
|
|
||||||
case "--version":
|
|
||||||
System.out.println(msg);
|
|
||||||
return;
|
|
||||||
case "--help":
|
|
||||||
System.out.println(msg);
|
|
||||||
return;
|
|
||||||
case "-help":
|
|
||||||
System.out.println(msg);
|
|
||||||
return;
|
|
||||||
case "-h":
|
|
||||||
System.out.println(msg);
|
|
||||||
return;
|
|
||||||
case "\\?":
|
|
||||||
System.out.println(msg);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(scu);
|
|
||||||
System.out.println(scn);
|
|
||||||
System.out.println(dcu);
|
|
||||||
System.out.println(dcn);
|
|
||||||
System.out.println(sq);
|
|
||||||
System.out.println(dt);
|
|
||||||
|
|
||||||
//return;
|
|
||||||
|
|
||||||
//force regstration
|
|
||||||
try {
|
|
||||||
Class.forName("com.ibm.as400.access.AS400JDBCDriver");
|
|
||||||
} catch (ClassNotFoundException cnf) {
|
|
||||||
System.out.println("The AS400 JDBC driver did not load");
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------establish connections-------------------------------------------------
|
|
||||||
//source database
|
|
||||||
try {
|
|
||||||
scon = DriverManager.getConnection(scu, scn, scp);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
System.out.println("issue connecting to source:");
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
//destination database
|
|
||||||
try {
|
|
||||||
dcon = DriverManager.getConnection(dcu, dcn, dcp);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
System.out.println("issue connecting to desctination:");
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------open resultset------------------------------------------------------------
|
|
||||||
try {
|
|
||||||
stmt = scon.createStatement();
|
|
||||||
rs = stmt.executeQuery(sq);
|
|
||||||
//while (rs.next()) {
|
|
||||||
// System.out.println(rs.getString("x"));
|
|
||||||
//}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
System.out.println("issue retrieving rows from source:");
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------build meta---------------------------------------------------------------
|
|
||||||
try {
|
|
||||||
cols = rs.getMetaData().getColumnCount();
|
|
||||||
System.out.println("number of cols: " + cols);
|
|
||||||
getv = new String[cols + 1];
|
|
||||||
dtn = new String[cols + 1];
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (int i = 1; i <= cols; i++){
|
|
||||||
dtn[i] = rs.getMetaData().getColumnTypeName(i);
|
|
||||||
System.out.println(rs.getMetaData().getColumnName(i) + ": " + dtn[i]);
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
//-------------------------------build & execute sql-------------------------------------------------------------
|
|
||||||
try {
|
|
||||||
while (rs.next()) {
|
|
||||||
r++;
|
|
||||||
t++;
|
|
||||||
nr = "";
|
|
||||||
for (int i = 1; i <= cols; i++){
|
|
||||||
nc = rs.getString(i);
|
|
||||||
if (dtn[i] == "DATE" && nc == "null") {
|
|
||||||
nc = "NULL";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (rs.wasNull()) {
|
|
||||||
nc = "NULL";
|
|
||||||
} else {
|
|
||||||
switch (dtn[i]){
|
|
||||||
case "VARCHAR":
|
|
||||||
nc = rs.getString(i).replace("'","''");
|
|
||||||
if (trim) { nc = nc.trim();}
|
|
||||||
nc = "'" + nc + "'";
|
|
||||||
break;
|
|
||||||
case "CLOB":
|
|
||||||
nc = rs.getString(i).replace("'","''");
|
|
||||||
if (trim) { nc = nc.trim();}
|
|
||||||
nc = "'" + nc + "'";
|
|
||||||
break;
|
|
||||||
case "CHAR":
|
|
||||||
nc = rs.getString(i).replace("'","''");
|
|
||||||
if (trim) { nc = nc.trim();}
|
|
||||||
nc = "'" + nc + "'";
|
|
||||||
break;
|
|
||||||
case "DATE":
|
|
||||||
nc = "'" + rs.getString(i) + "'";
|
|
||||||
if (nc == "'1/1/0001 12:00:00 AM'") {
|
|
||||||
nc = "NULL";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "TIME":
|
|
||||||
nc = "'" + rs.getString(i).replace("'","''") + "'";
|
|
||||||
break;
|
|
||||||
case "BIGINT":
|
|
||||||
nc = rs.getString(i);
|
|
||||||
default:
|
|
||||||
if (rs.getString(i) != "") {
|
|
||||||
nc = rs.getString(i);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nc = "NULL";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i != 1){
|
|
||||||
nr = nr + ",";
|
|
||||||
}
|
|
||||||
nr = nr + nc;
|
|
||||||
}
|
|
||||||
//add a comma to the end of the VALUES block to accomodate a new row
|
|
||||||
if (sql!="") {
|
|
||||||
sql = sql + ",";
|
|
||||||
}
|
|
||||||
//add the new row to the VALUES block
|
|
||||||
sql = sql + "(" + nr + ")";
|
|
||||||
if (r == 250){
|
|
||||||
r = 0;
|
|
||||||
sql = "INSERT INTO " + dt + " VALUES " + "\n" + sql;
|
|
||||||
//System.out.println(sql);
|
|
||||||
try {
|
|
||||||
stmtd = dcon.createStatement();
|
|
||||||
stmtd.executeUpdate(sql);
|
|
||||||
System.out.print("\r" + t);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.out.println(sql);
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
sql = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//if the sql is not empty, execute
|
|
||||||
if (sql != "") {
|
|
||||||
sql = "INSERT INTO " + dt + " VALUES " + "\n" + sql;
|
|
||||||
try {
|
|
||||||
stmtd = dcon.createStatement();
|
|
||||||
stmtd.executeUpdate(sql);
|
|
||||||
System.out.print("\r" + t);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.out.println(sql);
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
//System.out.println(sql);
|
|
||||||
|
|
||||||
//---------------------------------------close connections--------------------------------------------------------
|
|
||||||
try {
|
|
||||||
scon.close();
|
|
||||||
dcon.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
System.out.println("issue closing connections");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
129
deploy.sh
Executable file
129
deploy.sh
Executable file
@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "jrunner deployment script"
|
||||||
|
echo "========================="
|
||||||
|
echo ""
|
||||||
|
echo "Select deployment option:"
|
||||||
|
echo " 1) Local install (./jrunner/build/install)"
|
||||||
|
echo " 2) Global install (installDist + /usr/local/bin symlinks)"
|
||||||
|
echo " 3) Custom directory"
|
||||||
|
echo ""
|
||||||
|
read -p "Enter choice [1-3]: " choice
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
DEPLOY_MODE="local"
|
||||||
|
DEPLOY_DIR="./jrunner/build/install/jrunner"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
DEPLOY_MODE="global"
|
||||||
|
DEPLOY_DIR="/opt/jrunner"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
DEPLOY_MODE="custom"
|
||||||
|
read -e -p "Enter deployment directory (required): " DEPLOY_DIR
|
||||||
|
if [ -z "$DEPLOY_DIR" ]; then
|
||||||
|
echo "Error: Directory path is required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Expand tilde to home directory
|
||||||
|
DEPLOY_DIR="${DEPLOY_DIR/#\~/$HOME}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Invalid choice"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Prevent deleting critical system directories
|
||||||
|
case "${DEPLOY_DIR}" in
|
||||||
|
/|/bin|/boot|/dev|/etc|/lib|/lib64|/proc|/root|/run|/sbin|/sys|/usr|/var)
|
||||||
|
echo "Error: Cannot deploy to system directory: ${DEPLOY_DIR}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Building jrunner..."
|
||||||
|
./gradlew build
|
||||||
|
|
||||||
|
if [ "$DEPLOY_MODE" = "local" ]; then
|
||||||
|
echo "Installing locally with gradle..."
|
||||||
|
./gradlew installDist
|
||||||
|
echo "Installing jrq wrapper script..."
|
||||||
|
cp jrq "${DEPLOY_DIR}/bin/jrq"
|
||||||
|
chmod +x "${DEPLOY_DIR}/bin/jrq"
|
||||||
|
echo ""
|
||||||
|
echo "✅ Installed locally at: ${DEPLOY_DIR}"
|
||||||
|
echo "Run './jrunner/build/install/jrunner/bin/jrunner --help' to test"
|
||||||
|
echo "Run './jrunner/build/install/jrunner/bin/jrq' for query wrapper usage"
|
||||||
|
elif [ "$DEPLOY_MODE" = "global" ]; then
|
||||||
|
INSTALL_DIR="$(pwd)/jrunner/build/install/jrunner"
|
||||||
|
echo "Installing with gradle..."
|
||||||
|
./gradlew installDist
|
||||||
|
echo "Installing jrq wrapper script..."
|
||||||
|
cp jrq "${INSTALL_DIR}/bin/jrq"
|
||||||
|
chmod +x "${INSTALL_DIR}/bin/jrq"
|
||||||
|
echo "Creating symlinks at /usr/local/bin/..."
|
||||||
|
sudo ln -sf "${INSTALL_DIR}/bin/jrunner" /usr/local/bin/jrunner
|
||||||
|
sudo ln -sf "${INSTALL_DIR}/bin/jrq" /usr/local/bin/jrq
|
||||||
|
echo ""
|
||||||
|
echo "✅ Installed at: ${INSTALL_DIR}"
|
||||||
|
echo "Symlinked to /usr/local/bin/ — run 'jrunner --help' to test"
|
||||||
|
echo " run 'jrq' for query wrapper usage"
|
||||||
|
else
|
||||||
|
# Custom deployment
|
||||||
|
if [ ! -d "${DEPLOY_DIR}" ]; then
|
||||||
|
echo "Creating directory: ${DEPLOY_DIR}"
|
||||||
|
sudo mkdir -p "${DEPLOY_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Extracting to temporary location..."
|
||||||
|
sudo rm -rf /tmp/jrunner-deploy
|
||||||
|
sudo unzip -q jrunner/build/distributions/jrunner.zip -d /tmp/jrunner-deploy
|
||||||
|
|
||||||
|
echo "Deploying to ${DEPLOY_DIR}..."
|
||||||
|
sudo rm -rf "${DEPLOY_DIR}"/*
|
||||||
|
sudo mv /tmp/jrunner-deploy/jrunner/* "${DEPLOY_DIR}"/
|
||||||
|
sudo rm -rf /tmp/jrunner-deploy
|
||||||
|
|
||||||
|
echo "Fixing ownership..."
|
||||||
|
sudo chown -R $USER:$USER "${DEPLOY_DIR}"
|
||||||
|
|
||||||
|
# Copy jrq wrapper script
|
||||||
|
echo "Installing jrq wrapper script..."
|
||||||
|
sudo cp jrq "${DEPLOY_DIR}/bin/jrq"
|
||||||
|
sudo chmod +x "${DEPLOY_DIR}/bin/jrq"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Deployed to ${DEPLOY_DIR}"
|
||||||
|
echo "Run '${DEPLOY_DIR}/bin/jrunner --help' to test"
|
||||||
|
echo "Run '${DEPLOY_DIR}/bin/jrq' for query wrapper usage"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create ~/.jrqrc template if it doesn't exist
|
||||||
|
if [ ! -f "${HOME}/.jrqrc" ]; then
|
||||||
|
echo "Creating ~/.jrqrc config template..."
|
||||||
|
cat > "${HOME}/.jrqrc" <<'EOF'
|
||||||
|
# jrq configuration
|
||||||
|
# Uncomment and fill in the values below
|
||||||
|
|
||||||
|
# JDBC connection URL (required)
|
||||||
|
# Examples:
|
||||||
|
# AS/400: jdbc:as400://hostname
|
||||||
|
# PostgreSQL: jdbc:postgresql://hostname:5432/dbname
|
||||||
|
# SQL Server: jdbc:sqlserver://hostname:1433;databaseName=mydb
|
||||||
|
#JR_URL=""
|
||||||
|
|
||||||
|
# Database credentials (required)
|
||||||
|
#JR_USER=""
|
||||||
|
#JR_PASS=""
|
||||||
|
|
||||||
|
# Output format: csv or tsv (default: csv)
|
||||||
|
#JR_FORMAT="csv"
|
||||||
|
EOF
|
||||||
|
echo "Edit ~/.jrqrc to add your connection details before using jrq"
|
||||||
|
else
|
||||||
|
echo "~/.jrqrc already exists, skipping template creation"
|
||||||
|
fi
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
66
jrq
Executable file
66
jrq
Executable file
@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# jrq - jrunner query wrapper for piping to visidata
|
||||||
|
# Usage: jrq <sql_file> [format]
|
||||||
|
# Example: jrq query.sql
|
||||||
|
# jrq query.sql tsv
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
# Override these with environment variables or create ~/.jrqrc
|
||||||
|
CONFIG_FILE="${HOME}/.jrqrc"
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
JR_BIN="${JR_BIN:-${SCRIPT_DIR}/jrunner}"
|
||||||
|
JR_URL="${JR_URL:-}"
|
||||||
|
JR_USER="${JR_USER:-}"
|
||||||
|
JR_PASS="${JR_PASS:-}"
|
||||||
|
JR_FORMAT="${JR_FORMAT:-csv}"
|
||||||
|
|
||||||
|
# Load config file if it exists
|
||||||
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "Usage: jrq <sql_file> [format]"
|
||||||
|
echo ""
|
||||||
|
echo "Configuration (via environment variables or ~/.jrqrc):"
|
||||||
|
echo " JR_URL - JDBC connection URL (required)"
|
||||||
|
echo " JR_USER - Database username (required)"
|
||||||
|
echo " JR_PASS - Database password (required)"
|
||||||
|
echo " JR_BIN - Path to jrunner binary (default: /opt/jrunner/jrunner/build/install/jrunner/bin/jrunner)"
|
||||||
|
echo " JR_FORMAT - Output format: csv or tsv (default: csv)"
|
||||||
|
echo ""
|
||||||
|
echo "Example ~/.jrqrc:"
|
||||||
|
echo " JR_URL=\"jdbc:as400://s7830956\""
|
||||||
|
echo " JR_USER=\"username\""
|
||||||
|
echo " JR_PASS=\"password\""
|
||||||
|
echo ""
|
||||||
|
echo "Example usage:"
|
||||||
|
echo " jrq query.sql # outputs CSV to stdout"
|
||||||
|
echo " jrq query.sql | vd # pipe to visidata"
|
||||||
|
echo " jrq query.sql tsv # output TSV format"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SQL_FILE="$1"
|
||||||
|
FORMAT="${2:-$JR_FORMAT}"
|
||||||
|
|
||||||
|
# Validate configuration
|
||||||
|
if [ -z "$JR_URL" ] || [ -z "$JR_USER" ] || [ -z "$JR_PASS" ]; then
|
||||||
|
echo "Error: Missing database connection configuration" >&2
|
||||||
|
echo "Set JR_URL, JR_USER, and JR_PASS environment variables or create ~/.jrqrc" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate SQL file exists
|
||||||
|
if [ ! -f "$SQL_FILE" ]; then
|
||||||
|
echo "Error: SQL file not found: $SQL_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute jrunner
|
||||||
|
"$JR_BIN" -scu "$JR_URL" -scn "$JR_USER" -scp "$JR_PASS" -sq "$SQL_FILE" -f "$FORMAT"
|
||||||
@ -26,6 +26,8 @@ dependencies {
|
|||||||
//jdbc drivers
|
//jdbc drivers
|
||||||
implementation 'org.postgresql:postgresql:42.5.0'
|
implementation 'org.postgresql:postgresql:42.5.0'
|
||||||
implementation 'net.sf.jt400:jt400:11.0'
|
implementation 'net.sf.jt400:jt400:11.0'
|
||||||
|
implementation 'com.microsoft.sqlserver:mssql-jdbc:9.2.0.jre8'
|
||||||
|
implementation 'com.microsoft.sqlserver:mssql-jdbc_auth:9.2.0.x64'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
485
jrunner/src/main/java/jrunner/jrunner.java
Normal file
485
jrunner/src/main/java/jrunner/jrunner.java
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
package jrunner;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path ;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.*;
|
||||||
|
|
||||||
|
public class jrunner {
|
||||||
|
//static final String QUERY = "SELECT * from rlarp.osm LIMIT 100";
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
String scu = "";
|
||||||
|
String scn = "";
|
||||||
|
String scp = "";
|
||||||
|
String dcu = "";
|
||||||
|
String dcn = "";
|
||||||
|
String dcp = "";
|
||||||
|
String sq = "";
|
||||||
|
String dt = "";
|
||||||
|
Boolean trim = true;
|
||||||
|
Boolean clear = true;
|
||||||
|
Integer r = 0;
|
||||||
|
Integer t = 0;
|
||||||
|
String sql = "";
|
||||||
|
String nr = "";
|
||||||
|
String nc = "";
|
||||||
|
String nl = "\n";
|
||||||
|
Boolean queryMode = false;
|
||||||
|
String outputFormat = "csv";
|
||||||
|
String msg = "";
|
||||||
|
Connection scon = null;
|
||||||
|
Connection dcon = null;
|
||||||
|
Statement stmt = null;
|
||||||
|
Statement stmtd = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
String[] getv = null;
|
||||||
|
Integer cols = null;
|
||||||
|
String[] dtn = null;
|
||||||
|
Timestamp tsStart = null;
|
||||||
|
Timestamp tsEnd = null;
|
||||||
|
|
||||||
|
msg = "jrunner version 1.1";
|
||||||
|
msg = msg + nl + "-scu source jdbc url";
|
||||||
|
msg = msg + nl + "-scn source username";
|
||||||
|
msg = msg + nl + "-scp source passowrd";
|
||||||
|
msg = msg + nl + "-dcu destination jdbc url";
|
||||||
|
msg = msg + nl + "-dcn destination username";
|
||||||
|
msg = msg + nl + "-dcp destination passowrd";
|
||||||
|
msg = msg + nl + "-sq path to source query";
|
||||||
|
msg = msg + nl + "-dt fully qualified name of destination table";
|
||||||
|
msg = msg + nl + "-t trim text";
|
||||||
|
msg = msg + nl + "-c clear target table";
|
||||||
|
msg = msg + nl + "-f output format (csv, tsv, table, json) - default: csv";
|
||||||
|
msg = msg + nl + "--help info";
|
||||||
|
|
||||||
|
//---------------------------------------parse args into variables-------------------------------------------------
|
||||||
|
|
||||||
|
for (int i = 0; i < args.length; i = i +1 ){
|
||||||
|
switch (args[i]) {
|
||||||
|
//source connection string
|
||||||
|
case "-scu":
|
||||||
|
scu = args[i+1];
|
||||||
|
break;
|
||||||
|
//source username
|
||||||
|
case "-scn":
|
||||||
|
scn = args[i+1];
|
||||||
|
break;
|
||||||
|
//source password
|
||||||
|
//import java.time.*;
|
||||||
|
case "-scp":
|
||||||
|
scp = args[i+1];
|
||||||
|
break;
|
||||||
|
//destination connection string
|
||||||
|
case "-dcu":
|
||||||
|
dcu = args[i+1];
|
||||||
|
break;
|
||||||
|
//destination username
|
||||||
|
case "-dcn":
|
||||||
|
dcn = args[i+1];
|
||||||
|
break;
|
||||||
|
//destination password
|
||||||
|
case "-dcp":
|
||||||
|
dcp = args[i+1];
|
||||||
|
break;
|
||||||
|
//source query path
|
||||||
|
case "-sq":
|
||||||
|
try {
|
||||||
|
//sq = Files.readAllLines(Paths.get(args[i+1]));
|
||||||
|
sq = Files.readString(Paths.get(args[i+1]));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
//System.out.println(nl + "error reasing source sql file: " + printStackTrace());
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
//destination table name
|
||||||
|
case "-dt":
|
||||||
|
dt = args[i+1];
|
||||||
|
break;
|
||||||
|
case "-t":
|
||||||
|
trim = true;
|
||||||
|
break;
|
||||||
|
case "-c":
|
||||||
|
clear = true;
|
||||||
|
break;
|
||||||
|
case "-f":
|
||||||
|
outputFormat = args[i+1].toLowerCase();
|
||||||
|
break;
|
||||||
|
case "-v":
|
||||||
|
System.out.println(msg);
|
||||||
|
return;
|
||||||
|
case "--version":
|
||||||
|
System.out.println(msg);
|
||||||
|
return;
|
||||||
|
case "--help":
|
||||||
|
System.out.println(msg);
|
||||||
|
return;
|
||||||
|
case "-help":
|
||||||
|
System.out.println(msg);
|
||||||
|
return;
|
||||||
|
case "-h":
|
||||||
|
System.out.println(msg);
|
||||||
|
return;
|
||||||
|
case "\\?":
|
||||||
|
System.out.println(msg);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect query mode when destination flags are not provided
|
||||||
|
queryMode = dcu.isEmpty() && dcn.isEmpty() && dcp.isEmpty() && dt.isEmpty();
|
||||||
|
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println("------------db info---------------------------------------");
|
||||||
|
System.out.println("source db uri: " + scu);
|
||||||
|
System.out.println("source db username: " + scn);
|
||||||
|
System.out.println("destination db uri: " + dcu);
|
||||||
|
System.out.println("destination username: " + dcn);
|
||||||
|
System.out.println("------------source sql------------------------------------");
|
||||||
|
System.out.println(sq);
|
||||||
|
System.out.println("------------destination table-----------------------------");
|
||||||
|
System.out.println(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
//return;
|
||||||
|
|
||||||
|
//force regstration
|
||||||
|
try {
|
||||||
|
Class.forName("com.ibm.as400.access.AS400JDBCDriver");
|
||||||
|
} catch (ClassNotFoundException cnf) {
|
||||||
|
System.out.println("The AS400 JDBC driver did not load");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------establish connections-------------------------------------------------
|
||||||
|
//source database
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println("------------open database connections---------------------");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
scon = DriverManager.getConnection(scu, scn, scp);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.out.println("issue connecting to source:");
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println(" ✅ source database");
|
||||||
|
}
|
||||||
|
//destination database
|
||||||
|
if (!queryMode) {
|
||||||
|
try {
|
||||||
|
dcon = DriverManager.getConnection(dcu, dcn, dcp);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.out.println("issue connecting to destination:");
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
System.out.println(" ✅ destination database");
|
||||||
|
}
|
||||||
|
//----------------------------------------open resultset------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
stmt = scon.createStatement();
|
||||||
|
stmt.setFetchSize(10000);
|
||||||
|
tsStart = Timestamp.from(Instant.now());
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println(tsStart);
|
||||||
|
}
|
||||||
|
rs = stmt.executeQuery(sq);
|
||||||
|
//while (rs.next()) {
|
||||||
|
// System.out.println(rs.getString("x"));
|
||||||
|
//}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.out.println("issue retrieving rows from source:");
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------build meta---------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
cols = rs.getMetaData().getColumnCount();
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println("------------source metadata-------------------------------");
|
||||||
|
System.out.println("number of cols: " + cols);
|
||||||
|
}
|
||||||
|
getv = new String[cols + 1];
|
||||||
|
dtn = new String[cols + 1];
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (int i = 1; i <= cols; i++){
|
||||||
|
dtn[i] = rs.getMetaData().getColumnTypeName(i);
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println(" * " + rs.getMetaData().getColumnName(i) + ": " + dtn[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
//-------------------------clear the target table if requeted----------------------------------------------------
|
||||||
|
if (!queryMode && clear) {
|
||||||
|
System.out.println("------------clear target table----------------------------");
|
||||||
|
sql = "DELETE FROM " + dt;
|
||||||
|
try {
|
||||||
|
stmtd = dcon.createStatement();
|
||||||
|
System.out.println(" " + sql);
|
||||||
|
stmtd.executeUpdate(sql);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.out.println(sql);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queryMode) {
|
||||||
|
//-------------------------------output query results--------------------------------------------------------
|
||||||
|
try {
|
||||||
|
outputQueryResults(rs, cols, dtn, outputFormat);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("------------row count-------------------------------------");
|
||||||
|
//-------------------------------build & execute sql-------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
sql = "";
|
||||||
|
while (rs.next()) {
|
||||||
|
r++;
|
||||||
|
t++;
|
||||||
|
nr = "";
|
||||||
|
for (int i = 1; i <= cols; i++){
|
||||||
|
switch (dtn[i].toUpperCase()){
|
||||||
|
case "VARCHAR":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc = nc.replaceAll("'","''");
|
||||||
|
if (trim) { nc = nc.trim();}
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
break;
|
||||||
|
case "TEXT":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc = nc.replaceAll("'","''");
|
||||||
|
if (trim) { nc = nc.trim();}
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
break;
|
||||||
|
case "CHAR":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc = nc.replaceAll("'","''");
|
||||||
|
if (trim) { nc = nc.trim();}
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
break;
|
||||||
|
case "CLOB":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc = nc.replaceAll("'","''");
|
||||||
|
if (trim) { nc = nc.trim();}
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
break;
|
||||||
|
case "DATE":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
if (nc == "'1/1/0001 12:00:00 AM'") {
|
||||||
|
nc = "NULL";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "TIME":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc.replaceAll("'","''");
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
break;
|
||||||
|
case "TIMESTAMP":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nc.replaceAll("'","''");
|
||||||
|
nc = "'" + nc + "'";
|
||||||
|
break;
|
||||||
|
case "BIGINT":
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
nc = rs.getString(i);
|
||||||
|
if (rs.wasNull() || nc == null) {
|
||||||
|
nc = "NULL";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i != 1){
|
||||||
|
nr = nr + ",";
|
||||||
|
}
|
||||||
|
nr = nr + nc;
|
||||||
|
}
|
||||||
|
//add a comma to the end of the VALUES block to accomodate a new row
|
||||||
|
if (sql!="") {
|
||||||
|
sql = sql + ",";
|
||||||
|
}
|
||||||
|
//add the new row to the VALUES block
|
||||||
|
sql = sql + "(" + nr + ")";
|
||||||
|
if (r == 250){
|
||||||
|
r = 0;
|
||||||
|
sql = "INSERT INTO " + dt + " VALUES " + "\n" + sql;
|
||||||
|
//System.out.println(sql);
|
||||||
|
try {
|
||||||
|
stmtd = dcon.createStatement();
|
||||||
|
stmtd.executeUpdate(sql);
|
||||||
|
System.out.print("\r" + t);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.out.println(sql);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
sql = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if the sql is not empty, execute
|
||||||
|
if (sql != "") {
|
||||||
|
sql = "INSERT INTO " + dt + " VALUES " + "\n" + sql;
|
||||||
|
try {
|
||||||
|
stmtd = dcon.createStatement();
|
||||||
|
stmtd.executeUpdate(sql);
|
||||||
|
System.out.print("\r" + t);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.out.println(sql);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//System.out.println(sql);
|
||||||
|
|
||||||
|
//---------------------------------------close connections--------------------------------------------------------
|
||||||
|
try {
|
||||||
|
scon.close();
|
||||||
|
if (!queryMode && dcon != null) {
|
||||||
|
dcon.close();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.out.println("issue closing connections");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println(" rows written");
|
||||||
|
tsEnd = Timestamp.from(Instant.now());
|
||||||
|
System.out.println(tsStart);
|
||||||
|
System.out.println(tsEnd);
|
||||||
|
}
|
||||||
|
//long time = Duration.between(tsStart, tsEnd).toMillis();
|
||||||
|
//System.out.println("time elapsed: " + time);
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void outputQueryResults(ResultSet rs, int cols, String[] dtn, String format) throws SQLException {
|
||||||
|
switch (format) {
|
||||||
|
case "csv":
|
||||||
|
outputCSV(rs, cols);
|
||||||
|
break;
|
||||||
|
case "tsv":
|
||||||
|
outputTSV(rs, cols);
|
||||||
|
break;
|
||||||
|
case "table":
|
||||||
|
case "json":
|
||||||
|
default:
|
||||||
|
outputCSV(rs, cols);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void outputCSV(ResultSet rs, int cols) throws SQLException {
|
||||||
|
// Print header row
|
||||||
|
for (int i = 1; i <= cols; i++) {
|
||||||
|
if (i > 1) System.out.print(",");
|
||||||
|
System.out.print(escapeCSV(rs.getMetaData().getColumnName(i)));
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
// Print data rows
|
||||||
|
while (rs.next()) {
|
||||||
|
for (int i = 1; i <= cols; i++) {
|
||||||
|
if (i > 1) System.out.print(",");
|
||||||
|
String value = rs.getString(i);
|
||||||
|
if (rs.wasNull() || value == null) {
|
||||||
|
// Empty field for NULL
|
||||||
|
} else {
|
||||||
|
System.out.print(escapeCSV(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void outputTSV(ResultSet rs, int cols) throws SQLException {
|
||||||
|
// Print header row
|
||||||
|
for (int i = 1; i <= cols; i++) {
|
||||||
|
if (i > 1) System.out.print("\t");
|
||||||
|
System.out.print(escapeTSV(rs.getMetaData().getColumnName(i)));
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
// Print data rows
|
||||||
|
while (rs.next()) {
|
||||||
|
for (int i = 1; i <= cols; i++) {
|
||||||
|
if (i > 1) System.out.print("\t");
|
||||||
|
String value = rs.getString(i);
|
||||||
|
if (rs.wasNull() || value == null) {
|
||||||
|
// Empty field for NULL
|
||||||
|
} else {
|
||||||
|
System.out.print(escapeTSV(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeCSV(String value) {
|
||||||
|
if (value.contains(",") || value.contains("\"") || value.contains("\n") || value.contains("\r")) {
|
||||||
|
return "\"" + value.replaceAll("\"", "\"\"") + "\"";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeTSV(String value) {
|
||||||
|
return value.replaceAll("\t", " ").replaceAll("\n", " ").replaceAll("\r", " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
190
readme.md
Normal file
190
readme.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
## Quick Start
|
||||||
|
|
||||||
|
The easiest way to get started on a new system:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.hptrow.me/pt/jrunner.git
|
||||||
|
cd jrunner
|
||||||
|
./setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The setup script will:
|
||||||
|
- Check for Java 11+ (offers to install if missing)
|
||||||
|
- Verify Gradle wrapper is present
|
||||||
|
- Run a test build to ensure everything works
|
||||||
|
- Show you next steps
|
||||||
|
|
||||||
|
## Manual Installation
|
||||||
|
|
||||||
|
If you prefer to install dependencies manually:
|
||||||
|
|
||||||
|
### Install Java JDK
|
||||||
|
|
||||||
|
**Option 1: Package manager (recommended)**
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt update && sudo apt install openjdk-17-jdk
|
||||||
|
|
||||||
|
# Fedora/RHEL
|
||||||
|
sudo dnf install java-17-openjdk-devel
|
||||||
|
|
||||||
|
# Arch
|
||||||
|
sudo pacman -S jdk-openjdk
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Manual download**
|
||||||
|
Download from https://www.oracle.com/java/technologies/downloads/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://download.oracle.com/java/19/latest/jdk-19_linux-x64_bin.tar.gz
|
||||||
|
tar -xvf jdk-19_linux-x64_bin.tar.gz
|
||||||
|
sudo mv jdk-19.0.1 /opt/
|
||||||
|
export JAVA_HOME=/opt/jdk-19.0.1
|
||||||
|
export PATH=$PATH:$JAVA_HOME/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Test: `java --version`
|
||||||
|
|
||||||
|
### Install Gradle (optional)
|
||||||
|
Gradle wrapper (`gradlew`) is included in the repo, so manual Gradle installation is not required.
|
||||||
|
|
||||||
|
## build
|
||||||
|
```
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
## deploy
|
||||||
|
|
||||||
|
### using the deploy script (recommended)
|
||||||
|
|
||||||
|
Run the interactive deploy script:
|
||||||
|
```
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will prompt you to choose:
|
||||||
|
1. **Local install** - Fast, no sudo required, installs to `./jrunner/build/install/jrunner`
|
||||||
|
2. **Global install** - Installs to `/opt/jrunner` with symlink at `/usr/local/bin/jrunner`
|
||||||
|
3. **Custom directory** - Prompts for path with tab-completion support
|
||||||
|
|
||||||
|
The script builds, extracts to a temporary location, and only updates the target directory after the build succeeds. This ensures your existing deployment stays intact if the build fails.
|
||||||
|
|
||||||
|
### manual deployment
|
||||||
|
```
|
||||||
|
./gradlew build
|
||||||
|
sudo unzip jrunner/build/distributions/jrunner.zip -d /opt/
|
||||||
|
sudo ln -sf /opt/jrunner/bin/jrunner /usr/local/bin/jrunner
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for local testing:
|
||||||
|
```
|
||||||
|
./gradlew installDist
|
||||||
|
# Binary at: ./jrunner/build/install/jrunner/bin/jrunner
|
||||||
|
```
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
### Query Mode (new in v1.1)
|
||||||
|
|
||||||
|
Query mode outputs results to stdout for piping to visidata, pspg, or less. It activates automatically when destination flags are omitted.
|
||||||
|
|
||||||
|
**Basic query to CSV:**
|
||||||
|
```bash
|
||||||
|
jrunner -scu "jdbc:as400://hostname" -scn user -scp pass -sq query.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pipe to visidata:**
|
||||||
|
```bash
|
||||||
|
jrunner -scu "jdbc:as400://hostname" -scn user -scp pass -sq query.sql | visidata -f csv
|
||||||
|
```
|
||||||
|
|
||||||
|
**TSV format:**
|
||||||
|
```bash
|
||||||
|
jrunner -scu "jdbc:as400://hostname" -scn user -scp pass -sq query.sql -f tsv
|
||||||
|
```
|
||||||
|
|
||||||
|
**SQL Server example:**
|
||||||
|
```bash
|
||||||
|
jrunner -scu "jdbc:sqlserver://hostname:1433;databaseName=mydb" -scn user -scp pass -sq query.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**PostgreSQL example:**
|
||||||
|
```bash
|
||||||
|
jrunner -scu "jdbc:postgresql://hostname:5432/dbname" -scn user -scp pass -sq query.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### jrq - Query Mode Wrapper
|
||||||
|
|
||||||
|
The `jrq` wrapper script simplifies query mode usage by storing connection details in a config file, eliminating the need to type credentials repeatedly.
|
||||||
|
|
||||||
|
**Setup:**
|
||||||
|
|
||||||
|
Create `~/.jrqrc` with your database connection details:
|
||||||
|
```bash
|
||||||
|
JR_URL="jdbc:as400://s7830956"
|
||||||
|
JR_USER="myusername"
|
||||||
|
JR_PASS="mypassword"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
# Output to stdout
|
||||||
|
jrq query.sql
|
||||||
|
|
||||||
|
# Pipe to visidata
|
||||||
|
jrq query.sql | vd
|
||||||
|
|
||||||
|
# Use TSV format
|
||||||
|
jrq query.sql tsv
|
||||||
|
|
||||||
|
# Pipe TSV to less with column display
|
||||||
|
jrq query.sql tsv | less -S
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration options in ~/.jrqrc:**
|
||||||
|
- `JR_URL` - JDBC connection URL (required)
|
||||||
|
- `JR_USER` - Database username (required)
|
||||||
|
- `JR_PASS` - Database password (required)
|
||||||
|
- `JR_BIN` - Path to jrunner binary (default: /opt/jrunner/jrunner/build/install/jrunner/bin/jrunner)
|
||||||
|
- `JR_FORMAT` - Default output format: csv or tsv (default: csv)
|
||||||
|
|
||||||
|
**Note:** You can also set these as environment variables instead of using a config file:
|
||||||
|
```bash
|
||||||
|
export JR_URL="jdbc:as400://hostname"
|
||||||
|
export JR_USER="username"
|
||||||
|
export JR_PASS="password"
|
||||||
|
jrq query.sql | vd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration Mode
|
||||||
|
|
||||||
|
Full migration mode with both source and destination:
|
||||||
|
```bash
|
||||||
|
jrunner -scu jdbc:postgresql://source:5432/sourcedb \
|
||||||
|
-scn sourceuser \
|
||||||
|
-scp sourcepass \
|
||||||
|
-dcu jdbc:postgresql://dest:5432/destdb \
|
||||||
|
-dcn destuser \
|
||||||
|
-dcp destpass \
|
||||||
|
-sq query.sql \
|
||||||
|
-dt public.target_table
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command-line flags
|
||||||
|
|
||||||
|
**Source connection:**
|
||||||
|
- `-scu` - source JDBC URL
|
||||||
|
- `-scn` - source username
|
||||||
|
- `-scp` - source password
|
||||||
|
- `-sq` - path to source SQL query file
|
||||||
|
|
||||||
|
**Destination connection (migration mode only):**
|
||||||
|
- `-dcu` - destination JDBC URL
|
||||||
|
- `-dcn` - destination username
|
||||||
|
- `-dcp` - destination password
|
||||||
|
- `-dt` - fully qualified destination table name
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `-t` - trim text fields (default: true)
|
||||||
|
- `-c` - clear target table before insert (default: true)
|
||||||
|
- `-f` - output format: csv, tsv (query mode only, default: csv)
|
||||||
@ -8,4 +8,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
rootProject.name = 'jrunner'
|
rootProject.name = 'jrunner'
|
||||||
include('app')
|
include('jrunner')
|
||||||
|
|||||||
174
setup.sh
Executable file
174
setup.sh
Executable file
@ -0,0 +1,174 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "jrunner Development Environment Setup"
|
||||||
|
echo "====================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Color codes for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Check if running on Linux
|
||||||
|
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
||||||
|
echo -e "${YELLOW}Warning: This script is designed for Linux. You may need to manually install dependencies.${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to check if command exists
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get Java version
|
||||||
|
get_java_version() {
|
||||||
|
if command_exists java; then
|
||||||
|
java -version 2>&1 | head -n 1 | awk -F '"' '{print $2}' | awk -F '.' '{print $1}'
|
||||||
|
else
|
||||||
|
echo "0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Checking dependencies..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check Java
|
||||||
|
JAVA_VERSION=$(get_java_version)
|
||||||
|
if [ "$JAVA_VERSION" -ge 11 ]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Java $JAVA_VERSION detected"
|
||||||
|
java -version 2>&1 | head -n 1
|
||||||
|
JAVA_OK=true
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Java 11+ not found"
|
||||||
|
JAVA_OK=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Git
|
||||||
|
if command_exists git; then
|
||||||
|
echo -e "${GREEN}✓${NC} Git detected"
|
||||||
|
DEPENDENCIES_OK=true
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Git not found"
|
||||||
|
DEPENDENCIES_OK=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if gradlew exists
|
||||||
|
if [ -f "./gradlew" ]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Gradle wrapper found (no need to install Gradle)"
|
||||||
|
GRADLE_OK=true
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}!${NC} Gradle wrapper not found in current directory"
|
||||||
|
GRADLE_OK=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# If Java is missing, offer installation help
|
||||||
|
if [ "$JAVA_OK" = false ]; then
|
||||||
|
echo -e "${YELLOW}Java 11+ is required to build and run jrunner${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Installation options:"
|
||||||
|
echo ""
|
||||||
|
echo "1. Install OpenJDK via package manager (recommended):"
|
||||||
|
echo " Ubuntu/Debian: sudo apt update && sudo apt install openjdk-17-jdk"
|
||||||
|
echo " Fedora/RHEL: sudo dnf install java-17-openjdk-devel"
|
||||||
|
echo " Arch: sudo pacman -S jdk-openjdk"
|
||||||
|
echo ""
|
||||||
|
echo "2. Download Oracle JDK manually:"
|
||||||
|
echo " Visit: https://www.oracle.com/java/technologies/downloads/"
|
||||||
|
echo " Download JDK 17+ tarball and extract to /opt"
|
||||||
|
echo " Add to PATH: export JAVA_HOME=/opt/jdk-17 && export PATH=\$PATH:\$JAVA_HOME/bin"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Would you like to install OpenJDK via package manager? (requires sudo) [y/N]: " install_java
|
||||||
|
|
||||||
|
if [[ "$install_java" =~ ^[Yy]$ ]]; then
|
||||||
|
if command_exists apt; then
|
||||||
|
echo "Installing OpenJDK 17 via apt..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y openjdk-17-jdk
|
||||||
|
elif command_exists dnf; then
|
||||||
|
echo "Installing OpenJDK 17 via dnf..."
|
||||||
|
sudo dnf install -y java-17-openjdk-devel
|
||||||
|
elif command_exists pacman; then
|
||||||
|
echo "Installing OpenJDK via pacman..."
|
||||||
|
sudo pacman -S --noconfirm jdk-openjdk
|
||||||
|
else
|
||||||
|
echo -e "${RED}Could not detect package manager. Please install Java manually.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
JAVA_VERSION=$(get_java_version)
|
||||||
|
if [ "$JAVA_VERSION" -ge 11 ]; then
|
||||||
|
echo -e "${GREEN}✓ Java installed successfully${NC}"
|
||||||
|
JAVA_OK=true
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Java installation failed${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Please install Java manually and re-run this script."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for unzip (needed for deployment)
|
||||||
|
if ! command_exists unzip; then
|
||||||
|
echo -e "${YELLOW}Note: 'unzip' is recommended for deployment${NC}"
|
||||||
|
read -p "Install unzip? [y/N]: " install_unzip
|
||||||
|
if [[ "$install_unzip" =~ ^[Yy]$ ]]; then
|
||||||
|
if command_exists apt; then
|
||||||
|
sudo apt install -y unzip
|
||||||
|
elif command_exists dnf; then
|
||||||
|
sudo dnf install -y unzip
|
||||||
|
elif command_exists pacman; then
|
||||||
|
sudo pacman -S --noconfirm unzip
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}All required dependencies are installed!${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verify we're in the right directory
|
||||||
|
if [ ! -f "./gradlew" ]; then
|
||||||
|
echo -e "${RED}Error: gradlew not found. Are you in the jrunner project directory?${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make gradlew executable
|
||||||
|
chmod +x ./gradlew
|
||||||
|
|
||||||
|
echo "Testing build system..."
|
||||||
|
if ./gradlew --version > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}✓${NC} Gradle wrapper working"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Gradle wrapper failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Running test build..."
|
||||||
|
if ./gradlew build; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ Build successful!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Install locally for testing:"
|
||||||
|
echo " ./gradlew installDist"
|
||||||
|
echo " ./jrunner/build/install/jrunner/bin/jrunner --help"
|
||||||
|
echo ""
|
||||||
|
echo " 2. Or run the interactive deploy script:"
|
||||||
|
echo " ./deploy.sh"
|
||||||
|
echo ""
|
||||||
|
echo " 3. See readme.md for usage examples"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Build failed${NC}"
|
||||||
|
echo "Check the error messages above and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue
Block a user