Compare commits

..

No commits in common. "fc93be5a9d77dcf763cfd047c2fd2ef3ce09b8d1" and "85355efe8f7ff4a5b22335e279140aa3b8117bb1" have entirely different histories.

6 changed files with 114 additions and 732 deletions

View File

@ -28,16 +28,10 @@ gradle build
# Creates jrunner/build/distributions/jrunner.zip
```
Local install for testing (recommended):
Deploy to /opt (as documented in readme.md):
```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
sudo unzip jrunner/build/distributions/jrunner.zip -d /opt/
sudo ln -sf /opt/jrunner/bin/jrunner /usr/local/bin/jrunner
```
## Architecture
@ -45,33 +39,7 @@ Deploy using interactive script:
### 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
@ -100,59 +68,23 @@ 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)
- `-dcu` - destination JDBC URL
- `-dcn` - destination username
- `-dcp` - destination password
- `-sq` - path to source SQL query file
- `-dt` - fully qualified destination table name (migration mode only)
- `-dt` - fully qualified destination table name
- `-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)
- `-c` - clear target table before insert (default: true)
## 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.
### Batch Size
INSERT statements are batched at 250 rows (hardcoded at line 324). When the batch threshold is reached, sql is prepended with "INSERT INTO {table} VALUES" and executed.
### 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
- Result set fetch size is set to 10,000 rows (line 173)
- Progress counter prints with carriage return for real-time updates
- Timestamps captured at start (line 174) and end (line 368) for duration tracking

131
deploy.sh
View File

@ -1,40 +1,7 @@
#!/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
DEPLOY_DIR="${1:-/opt/jrunner}"
# Prevent deleting critical system directories
case "${DEPLOY_DIR}" in
@ -44,86 +11,32 @@ case "${DEPLOY_DIR}" in
;;
esac
echo ""
if [ ! -d "${DEPLOY_DIR}" ]; then
echo "Error: Directory does not exist: ${DEPLOY_DIR}"
echo "Create it first: sudo mkdir -p ${DEPLOY_DIR}"
exit 1
fi
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
sudo unzip -q jrunner/build/distributions/jrunner.zip -d /tmp/
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_DIR}"/
sudo rm -rf /tmp/jrunner
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}"
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"
# Only create symlink for /opt/jrunner
if [ "${DEPLOY_DIR}" = "/opt/jrunner" ]; then
echo "Creating symlink..."
sudo ln -sf /opt/jrunner/bin/jrunner /usr/local/bin/jrunner
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
echo "✅ Deployed to ${DEPLOY_DIR}"
echo "Run '${DEPLOY_DIR}/bin/jrunner --help' to test"

66
jrq
View File

@ -1,66 +0,0 @@
#!/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"

View File

@ -27,8 +27,6 @@ public class jrunner {
String nr = "";
String nc = "";
String nl = "\n";
Boolean queryMode = false;
String outputFormat = "csv";
String msg = "";
Connection scon = null;
Connection dcon = null;
@ -41,7 +39,7 @@ public class jrunner {
Timestamp tsStart = null;
Timestamp tsEnd = null;
msg = "jrunner version 1.1";
msg = "jrunner version 1.0";
msg = msg + nl + "-scu source jdbc url";
msg = msg + nl + "-scn source username";
msg = msg + nl + "-scp source passowrd";
@ -52,7 +50,6 @@ public class jrunner {
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-------------------------------------------------
@ -107,9 +104,6 @@ public class jrunner {
case "-c":
clear = true;
break;
case "-f":
outputFormat = args[i+1].toLowerCase();
break;
case "-v":
System.out.println(msg);
return;
@ -133,10 +127,6 @@ public class jrunner {
}
}
// 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);
@ -146,7 +136,6 @@ public class jrunner {
System.out.println(sq);
System.out.println("------------destination table-----------------------------");
System.out.println(dt);
}
//return;
@ -160,9 +149,7 @@ public class jrunner {
//-------------------------------------------establish connections-------------------------------------------------
//source database
if (!queryMode) {
System.out.println("------------open database connections---------------------");
}
try {
scon = DriverManager.getConnection(scu, scn, scp);
} catch (SQLException e) {
@ -170,11 +157,8 @@ public class jrunner {
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) {
@ -183,15 +167,12 @@ public class jrunner {
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"));
@ -205,10 +186,8 @@ public class jrunner {
//---------------------------------------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) {
@ -218,16 +197,14 @@ public class jrunner {
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) {
if (clear) {
System.out.println("------------clear target table----------------------------");
sql = "DELETE FROM " + dt;
try {
@ -240,15 +217,6 @@ public class jrunner {
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 {
@ -386,100 +354,22 @@ public class jrunner {
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", " ");
}
}

193
readme.md
View File

@ -1,52 +1,35 @@
## Quick Start
## install java jdk.
find downloads page and get latest tarball.
https://www.oracle.com/java/technologies/downloads/
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/
tar -xvf downloaded_file
```
move the extracted folder to /opt
put the extracted location in your path variable
```
export JAVA_HOME=/opt/jdk-19.0.1
export PATH=$PATH:$JAVA_HOME/bin
```
Test: `java --version`
`java --version` to test
### Install Gradle (optional)
## install gradle (optional)
Gradle wrapper (`gradlew`) is included in the repo, so manual Gradle installation is not required.
If you prefer to install Gradle system-wide:
```
wget https://services.gradle.org/distributions/gradle-8.5-bin.zip
unzip -d /opt/gradle gradle-8.5-bin.zip
export PATH=$PATH:/opt/gradle/gradle-8.5/bin
gradle -v
```
## clone this repo
```
git clone https://gitea.hptrow.me/pt/jrunner.git
cd jrunner
```
## build
```
@ -57,17 +40,22 @@ Gradle wrapper (`gradlew`) is included in the repo, so manual Gradle installatio
### using the deploy script (recommended)
Run the interactive deploy script:
First, create the deployment directory:
```
sudo mkdir -p /opt/jrunner
```
Then deploy:
```
# Deploy to /opt/jrunner (default, creates system-wide symlink)
./deploy.sh
# Deploy to custom location (for testing, no symlink)
sudo mkdir -p /opt/jrunner-test
./deploy.sh /opt/jrunner-test
```
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.
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. When deploying to `/opt/jrunner`, it creates a symlink at `/usr/local/bin/jrunner`.
### manual deployment
```
@ -76,115 +64,14 @@ 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
After deployment to default location:
```
jrunner -scu jdbc:postgresql://... -scn user -scp pass ...
```
**Pipe to visidata:**
```bash
jrunner -scu "jdbc:as400://hostname" -scn user -scp pass -sq query.sql | visidata -f csv
After deployment to custom location:
```
**TSV format:**
```bash
jrunner -scu "jdbc:as400://hostname" -scn user -scp pass -sq query.sql -f tsv
/opt/jrunner-test/bin/jrunner -scu jdbc:postgresql://... -scn user -scp pass ...
```
**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)

174
setup.sh
View File

@ -1,174 +0,0 @@
#!/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