<commit_before><?php
require_once __DIR__."/../core/classes/core.php";

function debug($msg){
	echo json_encode($msg,JSON_PRETTY_PRINT)."\n";
}

session_start();

define('OPTION_VOID',0);
define('OPTION_TAKES_VALUE',1);
define('OPTION_NOT_VOID',2); // never used without OPTION_TAKES_VALUE
define('OPTION_REQUIRES_VALUE',3); // 0b01 + 0b10, combined OPTION_TAKES_VALUE & OPTION_NOT_VOID
define('OPTION_REQUIRES_KEY_VALUE',4);
$long_options = [
	'help'=>OPTION_VOID,
	'execute'=>OPTION_VOID,
	'force'=>OPTION_VOID,
	'password'=>OPTION_TAKES_VALUE,
	'user'=>OPTION_REQUIRES_VALUE,
	'verbose'=>OPTION_VOID,
	'test'=>OPTION_VOID,
	'database'=>OPTION_REQUIRES_VALUE,
	'no-alter'=>OPTION_VOID,
	'no-create'=>OPTION_VOID,
	'no-drop'=>OPTION_VOID,
	'config'=>OPTION_REQUIRES_VALUE,
	'var'=>OPTION_REQUIRES_KEY_VALUE,
	'host'=>OPTION_REQUIRES_VALUE
];
$short_options = [
	'h'=>'help',
	'e'=>'execute',
	'f'=>'force',
	'p'=>'password',
	'u'=>'user',
	'v'=>'verbose',
	'd'=>'database',
	'c'=>'config',
	'w'=>'var',
	'h'=>'host'
];

$options = parse_options();

if(isset($options['help']) && $options['help']) help();

define('TEST_RUN',isset($options['test']) && $options['test']);

if(TEST_RUN) echo "Options:\n".json_encode($options)."\n";

if(isset($options['verbose'])
	&& $options['verbose']
	&& isset($options['config'])){
	echo "Configuration file: $options[config]\n";
}
if(isset($options['config'])){
	$config = load_config($options['config']);
	$configdir = dirname(realpath($options['config']));
} else {
	$config = [];
	$configdir = '.';
}

foreach($long_options as $name => $type){
	if(isset($options[$name])) $config[$name] = $options[$name];
	elseif(!isset($config[$name])) $config[$name] = false;
}
if(isset($options['files'])) $config['files'] = $options['files'];
if(empty($config['files']) && empty($config['batch'])) help(); //help exits
define('VERBOSE',$config['verbose']);
if(TEST_RUN) echo "Config:\n".json_encode($config)."\n";
$config['user'] = $config['user']!==false ? $config['user'] : get_current_user();
if($config['password'] === true) $config['password'] = <PASSWORD>();

run_config($config, $configdir);
exit;

function help(){
echo <<<'HELP'
Usage:
php dbtool.php [OPTIONS] SCHEMAFILE [SCHEMAFILE...]
php dbtool.php [OPTIONS] --config=CONFIGFILE

General Options:
  -h, --help                       Displays this help text.

  -cVALUE, --config=VALUE          Loads a config file.
  -dVALUE, --database=VALUE        An execution will use the given database, if a database isn't specified in the schemafile.
  -e, --execute                    Run the generated SQL to align the database with the provided schema.
  -f, --force                      Combined with -e: Run any SQL without asking first.
  --no-alter                       An execution will not include ALTER statements.
  --no-create                      An execution will not include CREATE statements.
  --no-drop                        An execution will not include DROP statements.
  -p[VALUE], --password[=VALUE]    Use given password or if not set, request password before connecting to the database.
  -uVALUE, --user=VALUE            Use the given username when connecting to the database.
  -v, --verbose                    Write extra descriptive output.
  -wKEY=VALUE, --var KEY=VALUE     Define a variable to be inserted in the schema.

  --test                           Run everything as usual, but without executing any SQL.

HELP;
exit;
}

function load_config($path){
	list($json,$error) = Core::load_file($path);
	if($error == 'invalid_path') fail("Failed to load configfile: $path",65);
	elseif(isset($error)) fail("Error parsing configfile as JSON: $error",66);
	return $json;
}

function ask_for_password(){
	$is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
	if($is_windows){
		echo "Hiding input of password isn't supported on Windows\n";
		if(!ask_continue('Are you sure you want to write your password visibly?',false)) return;
	}
	echo "Password: ";
	if(!$is_windows) system('stty -echo');
	$pw = rtrim(fgets(STDIN));
	if(!$is_windows) system('stty echo');
	echo "\n";
	return $pw;
}

function run_config($config, $configdir){
	list($objs, $error) = Core::load_and_run($config, $configdir);
	if($error == 'login_error') fail("Connection Error: Username or password incorrect", 2);

	$successful_results = [];
	$error_printed = false;
	$num = 0;
	foreach($objs as $obj){
		$num++;
		if(!empty($obj->error)){
			$batch_name = count($objs) > 1 ? "batch #$num" : 'config';
			echo "Error in $batch_name: $obj->error\n";
			$error_printed = true;
			continue;
		}
		$result = $obj->get_result();
		if(!show_error($result)) $successful_results[] = [$result,$obj,Config::get_instance(),$num];
		else $error_printed = true;
	}

	if($error_printed && !empty($successful_results)) echo str_repeat('=', 30)."\nProceeding with non-erroring batches.\n\n";

	foreach($successful_results as $pair){
		list($result,$obj,$batch_config,$batch_number) = $pair;
		Config::set_instance($batch_config);
		$batch_msg = "Batch $batch_number";
		$db = Config::get('database');
		if(!empty($db)){
			$batch_msg .= ", using database `$db`";
		}
		echo box($batch_msg);
		$changes = show_result($result);
		$database = Config::get('database');
		if(TEST_RUN) echo "[Test run, skipping execution]\n";
		elseif($changes && $config['execute'] && ($config['force'] || ask_continue("Do you want to execute changes".(empty($database)?'?':" on database `$database`?")))){
			$options = 0;
			if(!$config['no-alter']) $options |= Core::ALTER;
			if(!$config['no-create']) $options |= Core::CREATE;
			if(!$config['no-drop']) $options |= Core::DROP;
			$lines = $obj->execute($options);
			if(VERBOSE){
				echo "Execution:\n";
				if($lines){
					$messages = DB::get_messages();
					if(empty($messages)){
						echo "$lines SQL lines executed without errors.\n";
					} else {
						echo "The following errors was encountered:\n";
						foreach($messages as $msg) echo "$msg[text]\n";
					}
				} else {
					echo "No SQL lines were executed.\n";
				}
			}
		}
	}
}

function show_result($result){
	global $config;

	$intersection_tables = [];
	$file_tables = [];
	$unknown_types = [];

	foreach($result['tables'] as $table){
		if(empty($table['sql'])) continue;<commit_msg>fix array to string conversion when printing number of lines that was executed. fixes #24<commit_after><?php
require_once __DIR__."/../core/classes/core.php";

function debug($msg){
	echo json_encode($msg,JSON_PRETTY_PRINT)."\n";
}

session_start();

define('OPTION_VOID',0);
define('OPTION_TAKES_VALUE',1);
define('OPTION_NOT_VOID',2); // never used without OPTION_TAKES_VALUE
define('OPTION_REQUIRES_VALUE',3); // 0b01 + 0b10, combined OPTION_TAKES_VALUE & OPTION_NOT_VOID
define('OPTION_REQUIRES_KEY_VALUE',4);
$long_options = [
	'help'=>OPTION_VOID,
	'execute'=>OPTION_VOID,
	'force'=>OPTION_VOID,
	'password'=>OPTION_TAKES_VALUE,
	'user'=>OPTION_REQUIRES_VALUE,
	'verbose'=>OPTION_VOID,
	'test'=>OPTION_VOID,
	'database'=>OPTION_REQUIRES_VALUE,
	'no-alter'=>OPTION_VOID,
	'no-create'=>OPTION_VOID,
	'no-drop'=>OPTION_VOID,
	'config'=>OPTION_REQUIRES_VALUE,
	'var'=>OPTION_REQUIRES_KEY_VALUE,
	'host'=>OPTION_REQUIRES_VALUE
];
$short_options = [
	'h'=>'help',
	'e'=>'execute',
	'f'=>'force',
	'p'=>'password',
	'u'=>'user',
	'v'=>'verbose',
	'd'=>'database',
	'c'=>'config',
	'w'=>'var',
	'h'=>'host'
];

$options = parse_options();

if(isset($options['help']) && $options['help']) help();

define('TEST_RUN',isset($options['test']) && $options['test']);

if(TEST_RUN) echo "Options:\n".json_encode($options)."\n";

if(isset($options['verbose'])
	&& $options['verbose']
	&& isset($options['config'])){
	echo "Configuration file: $options[config]\n";
}
if(isset($options['config'])){
	$config = load_config($options['config']);
	$configdir = dirname(realpath($options['config']));
} else {
	$config = [];
	$configdir = '.';
}

foreach($long_options as $name => $type){
	if(isset($options[$name])) $config[$name] = $options[$name];
	elseif(!isset($config[$name])) $config[$name] = false;
}
if(isset($options['files'])) $config['files'] = $options['files'];
if(empty($config['files']) && empty($config['batch'])) help(); //help exits
define('VERBOSE',$config['verbose']);
if(TEST_RUN) echo "Config:\n".json_encode($config)."\n";
$config['user'] = $config['user']!==false ? $config['user'] : get_current_user();
if($config['password'] === true) $config['password'] = ask_for_password();

run_config($config, $configdir);
exit;

function help(){
echo <<<'HELP'
Usage:
php dbtool.php [OPTIONS] SCHEMAFILE [SCHEMAFILE...]
php dbtool.php [OPTIONS] --config=CONFIGFILE

General Options:
  -h, --help                       Displays this help text.

  -cVALUE, --config=VALUE          Loads a config file.
  -dVALUE, --database=VALUE        An execution will use the given database, if a database isn't specified in the schemafile.
  -e, --execute                    Run the generated SQL to align the database with the provided schema.
  -f, --force                      Combined with -e: Run any SQL without asking first.
  --no-alter                       An execution will not include ALTER statements.
  --no-create                      An execution will not include CREATE statements.
  --no-drop                        An execution will not include DROP statements.
  -p[VALUE], --password[=VALUE]    Use given password or if not set, request password before connecting to the database.
  -uVALUE, --user=VALUE            Use the given username when connecting to the database.
  -v, --verbose                    Write extra descriptive output.
  -wKEY=VALUE, --var KEY=VALUE     Define a variable to be inserted in the schema.

  --test                           Run everything as usual, but without executing any SQL.

HELP;
exit;
}

function load_config($path){
	list($json,$error) = Core::load_file($path);
	if($error == 'invalid_path') fail("Failed to load configfile: $path",65);
	elseif(isset($error)) fail("Error parsing configfile as JSON: $error",66);
	return $json;
}

function ask_for_password(){
	$is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
	if($is_windows){
		echo "Hiding input of password isn't supported on Windows\n";
		if(!ask_continue('Are you sure you want to write your password visibly?',false)) return;
	}
	echo "Password: ";
	if(!$is_windows) system('stty -echo');
	$pw = rtrim(fgets(STDIN));
	if(!$is_windows) system('stty echo');
	echo "\n";
	return $pw;
}

function run_config($config, $configdir){
	list($objs, $error) = Core::load_and_run($config, $configdir);
	if($error == 'login_error') fail("Connection Error: Username or password incorrect", 2);

	$successful_results = [];
	$error_printed = false;
	$num = 0;
	foreach($objs as $obj){
		$num++;
		if(!empty($obj->error)){
			$batch_name = count($objs) > 1 ? "batch #$num" : 'config';
			echo "Error in $batch_name: $obj->error\n";
			$error_printed = true;
			continue;
		}
		$result = $obj->get_result();
		if(!show_error($result)) $successful_results[] = [$result,$obj,Config::get_instance(),$num];
		else $error_printed = true;
	}

	if($error_printed && !empty($successful_results)) echo str_repeat('=', 30)."\nProceeding with non-erroring batches.\n\n";

	foreach($successful_results as $pair){
		list($result,$obj,$batch_config,$batch_number) = $pair;
		Config::set_instance($batch_config);
		$batch_msg = "Batch $batch_number";
		$db = Config::get('database');
		if(!empty($db)){
			$batch_msg .= ", using database `$db`";
		}
		echo box($batch_msg);
		$changes = show_result($result);
		$database = Config::get('database');
		if(TEST_RUN) echo "[Test run, skipping execution]\n";
		elseif($changes && $config['execute'] && ($config['force'] || ask_continue("Do you want to execute changes".(empty($database)?'?':" on database `$database`?")))){
			$options = 0;
			if(!$config['no-alter']) $options |= Core::ALTER;
			if(!$config['no-create']) $options |= Core::CREATE;
			if(!$config['no-drop']) $options |= Core::DROP;
			$lines = $obj->execute($options);
			if(VERBOSE){
				echo "Execution:\n";
				if($lines){
					$messages = DB::get_messages();
					if(empty($messages)){
						$line_count = count($lines);
						echo "$line_count SQL lines executed without errors.\n";
					} else {
						echo "The following errors was encountered:\n";
						foreach($messages as $msg) echo "$msg[text]\n";
					}
				} else {
					echo "No SQL lines were executed.\n";
				}
			}
		}
	}
}

function show_result($result){
	global $config;

	$intersection_tables = [];
	$file_tables = [];
	$unknown_types = [];

	foreach($result['tables'] as $table){
		if(empty($table['sql'])) continue;
{'fraction_non_alphanumeric': 0.18804365016606042, 'fraction_numerical': 0.003795666613949075, 'mean_word_length': 4.486767895878525, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 64, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2587219', 'n_tokens_mistral': 4411, 'n_tokens_neox': 4222, 'n_words': 961}