use strict;

use IO::File;

$Table::QUOTE = $Field::QUOTE = '`';

package Schema;

sub dump_command {
	my ($this, $host, $dbname, $user, $pass, $table) = @_;
	message('You must type the password interactively') if $pass;
	$host = $host ? "-h $host" : '';
	$user = defined $user ? "-u $user" : '';
	$pass = defined $pass ? "-p" : '';
	return defined $table ? qq{mysqldump $host $user $pass -d "$dbname" "$table"} :
		qq{mysqldump $host $user $pass -d "$dbname"};
}

sub data_src_database_word {
	return "database";
}

sub create_database {
	my ($this, $database, $data_src, $host, $user, $pass) = @_;
	message('You must type the password interactively') if $pass;
	$host = $host ? "-h $host" : "";
	$user = defined $user ? "-u $user" : '';
	$pass = defined $pass ? "-p" : '';
#	check that the database doesn't exist - mysqladmin doesn't!
#	return 0 if {map {lc $_, 1} @{Schema->data_sources("mysql")}}->{lc $database};
	return ! system("mysqladmin $host $user $pass create $database");
}

sub drop_database {
	my ($this, $database, $data_src, $host, $user, $pass) = @_;
	message('You must type the password interactively') if $pass;
	$host = $host ? "-h $host" : "";
	$user = defined $user ? "-u $user" : '';
	$pass = defined $pass ? "-p" : '';
	return ! system("yes | mysqladmin $host $user $pass drop $database");
}

package Table;

use Message;

sub alter_must_rebuild {
	my ($this, $diff, $new) = @_;
	my $must_rebuild = @{[$diff->removed_fields,
			      $diff->changed_fields,
			      $diff->renamed_fields,
			      $diff->removed_constraints,
			      $diff->changed_constraints,
			      $diff->renamed_constraints,
			      $diff->added_constraints]} ||
			      $diff->{order}{fields};
	return $must_rebuild;
}

sub realize {
	my ($this) = @_;
	$this->$_(undef) for qw(description title report); # TODO this is bad - need to inherit from Table.pm

	$this->recurse();

	if (map {defined $_ ? 1 : ()} @{ { map { $_->type, 1 } $this->fields } }{qw(varchar text blob)}) {
		# convert chars to varchars if any field is a varchar, text or blob
		my $warned_char_to_varchar;
		for ($this->fields) {
			my $type = $_->type;
			if (($type eq "char" || $type eq "char binary") && $_->size > 3) { # hack for binary chars in mysql
				$type =~ s/^char/varchar/;
				$_->type($type);
				comment("converting chars to varchars in table `$this->{name}'") unless $warned_char_to_varchar++;
			}
		}
	}

	# put a size-limit on blob / text indexes
	my $warned_limit;
	my $warned_oid;
#	comment ">>table: $this->{name} fields: @{[$_->field_names]}\n";
	for my $index ($this->indices) {
#		comment ">>  index: $_->{name}\n";
		for my $fieldref ($index->fields) {
#			comment ">>    field: $_->{name}\n";
			my $field_name = $fieldref->name;
			my $field = $this->field($field_name);
			if ($field && $field->type =~ /^text|blob$/ && !$fieldref->size) {
				$fieldref->size(255);
				comment("putting a 255 char limit on text / blob indexes in table `$this->{name}'") unless $warned_limit++;
			} elsif (!$field) {
				if ($field_name eq "oid") {
					comment("mysql does not support `oid' fields - dropping index ".$index->name) unless $warned_oid++;
					$this->remove_index($index->name);
				} else {
					comment("index ".$index->name." references unknown field ".$field_name);
				}
			}
		}
	}
}

package Field;

use Message;

my $warned_bogus_date = 0;
my $warned_timezone = 0;
sub _realize {
	my ($this, $type, $size) = @_;

	if ($this->default && $this->default =~ /^nextval\(/) {
		$this->autoincrement(1);
		$this->default(undef);
	}

	if (($type eq "char" || $type eq "varchar") && !$size) {
		return $type, 255;
	}

	# NOTE this char->text conversion is not reversible
	if (($type eq "char" || $type eq "varchar") && $size && $size > 255) {
		return "text";
	}

	if ($type eq "int") {
		if (!defined $size || $size == 11) {
			return "int", 11;
		} elsif ($size == 1) {
			return "tinyint", 4;
		} elsif ($size == 2) {
			return "smallint", 6;
		} elsif ($size == 3) {
			return "mediumint", 9;
		} elsif ($size > 4) {
			return "bigint", 20;
		}
	}

	if ($type eq "bool") {
		if ($this->default) {
			$this->default($this->default ? 1 : 0);
		}
		return "tinyint", 1;
	}

	if ($this->datetime or $this->date) {
		# convert 0001-01-01 back to 0000-00-00 so the schema verifies
		if ($this->default) {
			my $default = $this->default;
			if ($default =~ s/0001-01-01/0000-00-00/) {
				comment("converting 0001-01-01 back to bogus date 0000-00-00") unless $warned_bogus_date ++;
				$this->default($default);
			}
		}
	}

	if ($type eq "timestamp") {
		$size ||= 14;
		return $type, $size;
	}

	if ($type =~ /^(date)?timetz$/) {
		comment(qq{mysql doesn't support date or time types with a timezone.
  I will use one without a time zone instead.}) unless $warned_timezone ++;
		$type =~ s/tz$//;
		return $type, $size;
	}

	return;
}

sub _abstract {
	my ($this, $type, $size) = @_;

	if (!$this->null && defined $this->default &&
	    $this->numeric && $this->default == 0) {
		$this->default(undef);
	}

	if ($type eq "int") {
		return "int";
	}

	if ($type eq "tinyint") {
		if ($size == 1) {
			# this is dodgy, people might use tinyint(1)
			# for something OTHER than boolean too!
			if ($this->default) {
				$this->default($this->default ? "true" : "false");
			}
			return "bool";
		} else {
			return "int", 1;
		}
	}
	
	if ($type eq "smallint") {
		return "int", 2;
	}
	
	if ($type eq "mediumint") {
		return "int", 3;
	}
	
	if ($type eq "bigint") {
		return "int", 8;
	}

	if ($type eq "double") {
		return "double";
	}

	# not all the date/time types are properly abstracted yet

	if ($this->datetime or $this->date) {
		# mysql "timestamp" fields at least have a non-standard
		# date syntax (just 14 numbers)
		# fix_date also converts the bogus 0000-00-00 date to 0001-01-01
		if ($this->default) {
			$this->default(Field->fix_date($this->default));
		}
	}

	if ($type eq "timestamp") {
		undef $size if $size && $size == 14;
		return $type, $size;
	}

	return;
}

package Sequence;

my $warned_sequences = 0;
sub realize {
	comment(qq{mysql does not support sequences, only auto_inc fields
  dbischema cannot yet convert all PostgreSQL sequences to use auto_inc,
  but it does convert fields with default="nextval..." to use auto_inc.}) unless $warned_sequences++;
}

package Database;

sub realize {
	my ($this) = @_;
	$this->$_(undef) for qw(description title);
	$this->recurse();
	$this->clear_sequences();
}

1
