//
//  Field Definitions.m
//  SPMySQLFramework
//
//  Created by Rowan Beentje (rowan.beent.je) on February 2, 2012
//  Copyright (c) 2012 Rowan Beentje. All rights reserved.
//
//  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.
//
//  More info at <https://github.com/sequelpro/sequelpro>

#import "Field Definitions.h"
#import "SPMySQL Private APIs.h"

@interface SPMySQLResult (Field_Definitions_Private_API)

- (NSUInteger)_findCharsetMaxByteLengthPerCharForMySQLNumber:(NSUInteger)charsetnr;
- (NSString *)_charsetNameForMySQLNumber:(NSUInteger)charsetnr;
- (NSString *)_charsetCollationForMySQLNumber:(NSUInteger)charsetnr;
- (NSString *)_mysqlTypeToStringForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags withLength:(unsigned long long)length;
- (NSString *)_mysqlTypeToGroupForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags;

@end

#define MAGIC_BINARY_CHARSET_NR 63

const SPMySQLResultCharset SPMySQLCharsetMap[] =
{
	{1,   "big5","big5_chinese_ci", 1, 2},
	{2,   "latin2", "latin2_czech_cs", 1, 1},
	{3,   "dec8", "dec8_swedisch_ci", 1, 1},
	{4,   "cp850", "cp850_general_ci", 1, 1},
	{5,   "latin1", "latin1_german1_ci", 1, 1},
	{6,   "hp8", "hp8_english_ci", 1, 1},
	{7,   "koi8r", "koi8r_general_ci", 1, 1},
	{8,   "latin1", "latin1_swedish_ci", 1, 1},
	{9,   "latin2", "latin2_general_ci", 1, 1},
	{10,  "swe7", "swe7_swedish_ci", 1, 1},
	{11,  "ascii", "ascii_general_ci", 1, 1},
	{12,  "ujis", "ujis_japanese_ci", 1, 3},
	{13,  "sjis", "sjis_japanese_ci", 1, 2},
	{14,  "cp1251", "cp1251_bulgarian_ci", 1, 1},
	{15,  "latin1", "latin1_danish_ci", 1, 1},
	{16,  "hebrew", "hebrew_general_ci", 1, 1},
	{17,  "filename", "filename", 1, 5},
	{18,  "tis620", "tis620_thai_ci", 1, 1},
	{19,  "euckr", "euckr_korean_ci", 1, 2},
	{20,  "latin7", "latin7_estonian_cs", 1, 1},
	{21,  "latin2", "latin2_hungarian_ci", 1, 1},
	{22,  "koi8u", "koi8u_general_ci", 1, 1},
	{23,  "cp1251", "cp1251_ukrainian_ci", 1, 1},
	{24,  "gb2312", "gb2312_chinese_ci", 1, 2},
	{25,  "greek", "greek_general_ci", 1, 1},
	{26,  "cp1250", "cp1250_general_ci", 1, 1},
	{27,  "latin2", "latin2_croatian_ci", 1, 1},
	{28,  "gbk", "gbk_chinese_ci", 1, 2},
	{29,  "cp1257", "cp1257_lithunian_ci", 1, 1},
	{30,  "latin5", "latin5_turkish_ci", 1, 1},
	{31,  "latin1", "latin1_german2_ci", 1, 1},
	{32,  "armscii8", "armscii8_general_ci", 1, 1},
	{33,  "utf8", "utf8_general_ci", 1, 3}, //mb3
	{34,  "cp1250", "cp1250_czech_cs", 1, 1},
	{35,  "ucs2", "ucs2_general_ci", 2, 2},
	{36,  "cp866", "cp866_general_ci", 1, 1},
	{37,  "keybcs2", "keybcs2_general_ci", 1, 1},
	{38,  "macce", "macce_general_ci", 1, 1},
	{39,  "macroman", "macroman_general_ci", 1, 1},
	{40,  "cp852", "cp852_general_ci", 1, 1},
	{41,  "latin7", "latin7_general_ci", 1, 1},
	{42,  "latin7", "latin7_general_cs", 1, 1},
	{43,  "macce", "macce_bin", 1, 1},
	{44,  "cp1250", "cp1250_croatian_ci", 1, 1},
	{45,  "utf8", "utf8_general_ci", 1, 4}, //mb4
	{46,  "utf8", "utf8_bin", 1, 4}, //mb4
	{47,  "latin1", "latin1_bin", 1, 1},
	{48,  "latin1", "latin1_general_ci", 1, 1},
	{49,  "latin1", "latin1_general_cs", 1, 1},
	{50,  "cp1251", "cp1251_bin", 1, 1},
	{51,  "cp1251", "cp1251_general_ci", 1, 1},
	{52,  "cp1251", "cp1251_general_cs", 1, 1},
	{53,  "macroman", "macroman_bin", 1, 1},

	{57,  "cp1256", "cp1256_general_ci", 1, 1},
	{58,  "cp1257", "cp1257_bin", 1, 1},
	{59,  "cp1257", "cp1257_general_ci", 1, 1},
#warning 60 is "utf32_general_ci" and "armascii8" is at 64 in mysql 5.5.59!?
	{60,  "armascii8", "armascii8_bin", 1, 1},

	{63,  "binary", "binary", 1, 1},

	{65,  "ascii", "ascii_bin", 1, 1},
	{66,  "cp1250", "cp1250_bin", 1, 1},
	{67,  "cp1256", "cp1256_bin", 1, 1},
	{68,  "cp866", "cp866_bin", 1, 1},
	{69,  "dec8", "dec8_bin", 1, 1},
	{70,  "greek", "greek_bin", 1, 1},
	{71,  "hebew", "hebrew_bin", 1, 1},
	{72,  "hp8", "hp8_bin", 1, 1},
	{73,  "keybcs2", "keybcs2_bin", 1, 1},
	{74,  "koi8r", "koi8r_bin", 1, 1},
	{75,  "koi8u", "koi8u_bin", 1, 1},

	{77,  "latin2", "latin2_bin", 1, 1},
	{78,  "latin5", "latin5_bin", 1, 1},
	{79,  "latin7", "latin7_bin", 1, 1},
	{80,  "cp850", "cp850_bin", 1, 1},
	{81,  "cp852", "cp852_bin", 1, 1},
	{82,  "swe7", "swe7_bin", 1, 1},
	{83,  "utf8", "utf8_bin", 1, 3},
	{84,  "big5", "big5_bin", 1, 2},
	{85,  "euckr", "euckr_bin", 1, 2},
	{86,  "gb2312", "gb2312_bin", 1, 2},
	{87,  "gbk", "gbk_bin", 1, 2},
	{88,  "sjis", "sjis_bin", 1, 2},
	{89,  "tis620", "tis620_bin", 1, 1},
	{90,  "ucs2", "ucs2_bin", 2, 2},
	{91,  "ujis", "ujis_bin", 1, 3},
	{92,  "geostd8", "geostd8_general_ci", 1, 1},
	{93,  "geostd8", "geostd8_bin", 1, 1},
	{94,  "latin1", "latin1_spanish_ci", 1, 1},
	{95,  "cp932", "cp932_japanese_ci", 1, 2},
	{96,  "cp932", "cp932_bin", 1, 2},
	{97,  "eucjpms", "eucjpms_japanese_ci", 1, 3},
	{98,  "eucjpms", "eucjpms_bin", 1, 3},
	{99,  "cp1250", "cp1250_polish_ci", 1, 1},

	{128, "ucs2", "ucs2_unicode_ci", 2, 2},
	{129, "ucs2", "ucs2_icelandic_ci", 2, 2},
	{130, "ucs2", "ucs2_latvian_ci", 2, 2},
	{131, "ucs2", "ucs2_romanian_ci", 2, 2},
	{132, "ucs2", "ucs2_slovenian_ci", 2, 2},
	{133, "ucs2", "ucs2_polish_ci", 2, 2},
	{134, "ucs2", "ucs2_estonian_ci", 2, 2},
	{135, "ucs2", "ucs2_spanish_ci", 2, 2},
	{136, "ucs2", "ucs2_swedish_ci", 2, 2},
	{137, "ucs2", "ucs2_turkish_ci", 2, 2},
	{138, "ucs2", "ucs2_czech_ci", 2, 2},
	{139, "ucs2", "ucs2_danish_ci", 2, 2},
	{140, "ucs2", "ucs2_lithunian_ci", 2, 2},
	{141, "ucs2", "ucs2_slovak_ci", 2, 2},
	{142, "ucs2", "ucs2_spanish2_ci", 2, 2},
	{143, "ucs2", "ucs2_roman_ci", 2, 2},
	{144, "ucs2", "ucs2_persian_ci", 2, 2},
	{145, "ucs2", "ucs2_esperanto_ci", 2, 2},
	{146, "ucs2", "ucs2_hungarian_ci", 2, 2},
	{147, "ucs2", "ucs2_sinhala_ci", 2, 2},

	{192, "utf8mb3", "utf8mb3_general_ci", 1, 3},
	{193, "utf8mb3", "utf8mb3_icelandic_ci", 1, 3},
	{194, "utf8mb3", "utf8mb3_latvian_ci", 1, 3},
	{195, "utf8mb3", "utf8mb3_romanian_ci", 1, 3},
	{196, "utf8mb3", "utf8mb3_slovenian_ci", 1, 3},
	{197, "utf8mb3", "utf8mb3_polish_ci", 1, 3},
	{198, "utf8mb3", "utf8mb3_estonian_ci", 1, 3},
	{199, "utf8mb3", "utf8mb3_spanish_ci", 1, 3},
	{200, "utf8mb3", "utf8mb3_swedish_ci", 1, 3},
	{201, "utf8mb3", "utf8mb3_turkish_ci", 1, 3},
	{202, "utf8mb3", "utf8mb3_czech_ci", 1, 3},
	{203, "utf8mb3", "utf8mb3_danish_ci", 1, 3},
	{204, "utf8mb3", "utf8mb3_lithunian_ci", 1, 3},
	{205, "utf8mb3", "utf8mb3_slovak_ci", 1, 3},
	{206, "utf8mb3", "utf8mb3_spanish2_ci", 1, 3},
	{207, "utf8mb3", "utf8mb3_roman_ci", 1, 3},
	{208, "utf8mb3", "utf8mb3_persian_ci", 1, 3},
	{209, "utf8mb3", "utf8mb3_esperanto_ci", 1, 3},
	{210, "utf8mb3", "utf8mb3_hungarian_ci", 1, 3},
	{211, "utf8mb3", "utf8mb3_sinhala_ci", 1, 3},

	{224, "utf8", "utf8_unicode_ci", 1, 4}, //mb4
	{225, "utf8", "utf8_icelandic_ci", 1, 4}, //mb4
	{226, "utf8", "utf8_latvian_ci", 1, 4}, //mb4
	{227, "utf8", "utf8_romanian_ci", 1, 4}, //mb4
	{228, "utf8", "utf8_slovenian_ci", 1, 4}, //mb4
	{229, "utf8", "utf8_polish_ci", 1, 4}, //mb4
	{230, "utf8", "utf8_estonian_ci", 1, 4}, //mb4
	{231, "utf8", "utf8_spanish_ci", 1, 4}, //mb4
	{232, "utf8", "utf8_swedish_ci", 1, 4}, //mb4
	{233, "utf8", "utf8_turkish_ci", 1, 4}, //mb4
	{234, "utf8", "utf8_czech_ci", 1, 4}, //mb4
	{235, "utf8", "utf8_danish_ci", 1, 4}, //mb4
	{236, "utf8", "utf8_lithuanian_ci", 1, 4}, //mb4
	{237, "utf8", "utf8_slovak_ci", 1, 4}, //mb4
	{238, "utf8", "utf8_spanish2_ci", 1, 4}, //mb4
	{239, "utf8", "utf8_roman_ci", 1, 4}, //mb4
	{240, "utf8", "utf8_persian_ci", 1, 4}, //mb4
	{241, "utf8", "utf8_esperanto_ci", 1, 4}, //mb4
	{242, "utf8", "utf8_hungarian_ci", 1, 4}, //mb4
	{243, "utf8", "utf8_sinhala_ci", 1, 4}, //mb4

	{254, "utf8mb3", "utf8mb3_general_cs", 1, 3},

	{0, NULL, NULL, 0, 0}
};

#pragma mark -

@implementation SPMySQLResult (Field_Definitions)

/**
 * Return an array of NSDictionaries, each containing information about one of
 * the columns in the result set.
 * MySQL returns non-valid details as empty strings - these are converted to
 * unset entries in the dictionary.
 */
- (NSArray *)fieldDefinitions
{
	NSUInteger i;
	NSMutableArray *theFieldDefinitions = [NSMutableArray array];
	NSMutableDictionary *eachField;
	MYSQL_FIELD mysqlField;

	for (i = 0; i < numberOfFields; i++) {
		eachField = [NSMutableDictionary dictionary];
		mysqlField = fieldDefinitions[i];

		// Record the original column position within the result set
		[eachField setObject:[NSString stringWithFormat:@"%llu", (unsigned long long)i] forKey:@"datacolumnindex"];

		// mysqlField.name might point to an empty string or NULL (theoretically).
		// _stringWithBytes:... will return @"" if either bytes is NULL or length is 0.
		// For now let's interpret (bytes != NULL) as a valid string (possibly empty)
		// and otherwise as 'value not set'.
		
		// Record the column name, or alias if one is being used
		if (mysqlField.name) {
			[eachField setObject:[self _lossyStringWithBytes:mysqlField.name length:mysqlField.name_length wasLossy:NULL] forKey:@"name"];
		}
		
		// Record the original column name if using an alias
		if (mysqlField.org_name) {
			[eachField setObject:[self _stringWithBytes:mysqlField.org_name length:mysqlField.org_name_length] forKey:@"org_name"];
		}
		
		// If the column had an underlying table, record the table name, respecting aliases
		if (mysqlField.table) {
			[eachField setObject:[self _stringWithBytes:mysqlField.table length:mysqlField.table_length] forKey:@"table"];
		}

		// If the column had an underlying table, record the original table name, ignoring aliases
		if (mysqlField.org_table) {
			[eachField setObject:[self _stringWithBytes:mysqlField.org_table length:mysqlField.org_table_length] forKey:@"org_table"];
		}

		// If the column had an underlying database, record the database name
		if (mysqlField.db) {
			[eachField setObject:[self _stringWithBytes:mysqlField.db length:mysqlField.db_length] forKey:@"db"];
		}

		// Width of column (minimum real length in bytes)
		[eachField setObject:[NSNumber numberWithUnsignedLongLong:mysqlField.length] forKey:@"byte_length"];

		// Width of column (as in create)
		// TODO: Discuss the logic of this with Hans-Jörg Bibiko; is this related to max_byte_length?
		[eachField setObject:[NSNumber numberWithUnsignedLongLong:(mysqlField.length/[self _findCharsetMaxByteLengthPerCharForMySQLNumber:mysqlField.charsetnr])] forKey:@"char_length"];

		// Max width (bytes) for selected set.  Note that this will be 0 for streaming results.
		[eachField setObject:[NSNumber numberWithUnsignedLongLong:mysqlField.max_length] forKey:@"max_byte_length"];

		// Bit-flags that describe the field, in entirety and split out
		[eachField setObject:[NSNumber numberWithUnsignedInt:mysqlField.flags] forKey:@"flags"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & NOT_NULL_FLAG) ? YES : NO] forKey:@"null"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & PRI_KEY_FLAG) ? YES : NO] forKey:@"PRI_KEY_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & UNIQUE_KEY_FLAG) ? YES : NO] forKey:@"UNIQUE_KEY_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & MULTIPLE_KEY_FLAG) ? YES : NO] forKey:@"MULTIPLE_KEY_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & BLOB_FLAG) ? YES : NO] forKey:@"BLOB_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & UNSIGNED_FLAG) ? YES : NO] forKey:@"UNSIGNED_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & ZEROFILL_FLAG) ? YES : NO] forKey:@"ZEROFILL_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & BINARY_FLAG) ? YES : NO] forKey:@"BINARY_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & ENUM_FLAG) ? YES : NO] forKey:@"ENUM_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & AUTO_INCREMENT_FLAG) ? YES : NO] forKey:@"AUTO_INCREMENT_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & SET_FLAG) ? YES : NO] forKey:@"SET_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & NUM_FLAG) ? YES : NO] forKey:@"NUM_FLAG"];
		[eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & PART_KEY_FLAG) ? YES : NO] forKey:@"PART_KEY_FLAG"];

		// For numeric fields, record the number of decimals
		[eachField setObject:[NSNumber numberWithUnsignedInteger:mysqlField.decimals] forKey:@"decimals"];

		// Character set details
		[eachField setObject:[NSNumber numberWithUnsignedInteger:mysqlField.charsetnr] forKey:@"charsetnr"];
		[eachField setObject:[self _charsetNameForMySQLNumber:mysqlField.charsetnr] forKey:@"charset_name"];
		[eachField setObject:[self _charsetCollationForMySQLNumber:mysqlField.charsetnr] forKey:@"charset_collation"];

		// Table type
		[eachField setObject:[self _mysqlTypeToStringForType:mysqlField.type
		                                       withCharsetNr:mysqlField.charsetnr
		                                           withFlags:mysqlField.flags
		                                          withLength:mysqlField.length]
		              forKey:@"type"];

		// Table type group
		[eachField setObject:[self _mysqlTypeToGroupForType:mysqlField.type
		                                      withCharsetNr:mysqlField.charsetnr
		                                          withFlags:mysqlField.flags]
		              forKey:@"typegrouping"];

		[theFieldDefinitions addObject:eachField];
	}

	return theFieldDefinitions;
}

@end

#pragma mark -
#pragma mark Field defintion internals

@implementation SPMySQLResult (Field_Definitions_Private_API)

/**
 * Return the maximum byte length to store a char by using
 * a specific mysql_charsetnr
 */
- (NSUInteger)_findCharsetMaxByteLengthPerCharForMySQLNumber:(NSUInteger)charsetnr
{
	const SPMySQLResultCharset *c = SPMySQLCharsetMap;

	do {
		if (c->nr == charsetnr) return c->char_maxlen;
		++c;
	} while (c[0].nr != 0);

	return 1;
}

/**
 * Convert a mysql_charsetnr into a charset name as string
 */
- (NSString *)_charsetNameForMySQLNumber:(NSUInteger)charsetnr
{
	const SPMySQLResultCharset *c = SPMySQLCharsetMap;

	do {
		if (c->nr == charsetnr) return [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding];
		++c;
	} while (c[0].nr != 0);

	return @"UNKNOWN";
}

/**
 * Convert a mysql_charsetnr into a collation name as string
 */
- (NSString *)_charsetCollationForMySQLNumber:(NSUInteger)charsetnr
{
	const SPMySQLResultCharset *c = SPMySQLCharsetMap;

	do {
		if (c->nr == charsetnr) return [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding];
		++c;
	} while (c[0].nr != 0);

	return @"UNKNOWN";
}

/**
 * Convert a mysql_type to a string
 */
- (NSString *)_mysqlTypeToStringForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags withLength:(unsigned long long)length
{
	switch (type) {

		case MYSQL_TYPE_BIT:
			return @"BIT";

		case MYSQL_TYPE_DECIMAL:
		case MYSQL_TYPE_NEWDECIMAL:
			return @"DECIMAL";

		case MYSQL_TYPE_TINY:
			return @"TINYINT";

		case MYSQL_TYPE_SHORT:
			return @"SMALLINT";

		case MYSQL_TYPE_LONG:
			return @"INT";

		case MYSQL_TYPE_FLOAT:
			return @"FLOAT";

		case MYSQL_TYPE_DOUBLE:
			return @"DOUBLE";

		case MYSQL_TYPE_NULL:
			return @"NULL";

		case MYSQL_TYPE_TIMESTAMP:
			return @"TIMESTAMP";

		case MYSQL_TYPE_LONGLONG:
			return @"BIGINT";

		case MYSQL_TYPE_INT24:
			return @"MEDIUMINT";

		case MYSQL_TYPE_DATE:
			return @"DATE";

		case MYSQL_TYPE_TIME:
			return @"TIME";

		case MYSQL_TYPE_DATETIME:
			return @"DATETIME";

		case MYSQL_TYPE_TINY_BLOB:// should no appear over the wire
		case MYSQL_TYPE_MEDIUM_BLOB:// should no appear over the wire
		case MYSQL_TYPE_LONG_BLOB:// should no appear over the wire
		case MYSQL_TYPE_BLOB:
		{
			BOOL isBlob = (charsetnr == MAGIC_BINARY_CHARSET_NR);
			switch (length/[self _findCharsetMaxByteLengthPerCharForMySQLNumber:charsetnr]) {
				case 255: return isBlob? @"TINYBLOB":@"TINYTEXT";
				case 65535: return isBlob? @"BLOB":@"TEXT";
				case 16777215: return isBlob? @"MEDIUMBLOB":@"MEDIUMTEXT";
				case 4294967295: return isBlob? @"LONGBLOB":@"LONGTEXT";
				default:
					switch (length) {
						case 255: return isBlob? @"TINYBLOB":@"TINYTEXT";
						case 65535: return isBlob? @"BLOB":@"TEXT";
						case 16777215: return isBlob? @"MEDIUMBLOB":@"MEDIUMTEXT";
						case 4294967295: return isBlob? @"LONGBLOB":@"LONGTEXT";
						default:
							return @"UNKNOWN";
					}
			}
		}

		case MYSQL_TYPE_VAR_STRING:
			if (flags & ENUM_FLAG) {
				return @"ENUM";
			}
			if (flags & SET_FLAG) {
				return @"SET";
			}
			if (charsetnr == MAGIC_BINARY_CHARSET_NR) {
				return @"VARBINARY";
			}
			return @"VARCHAR";

		case MYSQL_TYPE_STRING:
			if (flags & ENUM_FLAG) {
				return @"ENUM";
			}
			if (flags & SET_FLAG) {
				return @"SET";
			}
			if ((flags & BINARY_FLAG) && charsetnr == MAGIC_BINARY_CHARSET_NR) {
				return @"BINARY";
			}
			return @"CHAR";

		case MYSQL_TYPE_ENUM:
			/* This should never happen */
			return @"ENUM";

		case MYSQL_TYPE_YEAR:
			return @"YEAR";

		case MYSQL_TYPE_SET:
			/* This should never happen */
			return @"SET";

		case MYSQL_TYPE_GEOMETRY:
			return @"GEOMETRY";
			
		case MYSQL_TYPE_JSON:
			return @"JSON";

		default:
			return @"UNKNOWN";
	}
}

/**
 * Merge mysql_types into type groups
 */
- (NSString *)_mysqlTypeToGroupForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags
{
	switch(type){

		case MYSQL_TYPE_BIT:
			return @"bit";

		case MYSQL_TYPE_TINY:
		case MYSQL_TYPE_SHORT:
		case MYSQL_TYPE_LONG:
		case MYSQL_TYPE_LONGLONG:
		case MYSQL_TYPE_INT24:
			return @"integer";

		case MYSQL_TYPE_FLOAT:
		case MYSQL_TYPE_DOUBLE:
		case MYSQL_TYPE_DECIMAL:
		case MYSQL_TYPE_NEWDECIMAL:
			return @"float";

		case MYSQL_TYPE_YEAR:
		case MYSQL_TYPE_DATETIME:
		case MYSQL_TYPE_TIME:
		case MYSQL_TYPE_DATE:
		case MYSQL_TYPE_TIMESTAMP:
			return @"date";

		case MYSQL_TYPE_VAR_STRING:
			if (flags & ENUM_FLAG) {
				return @"enum";
			}
			if (flags & SET_FLAG) {
				return @"enum";
			}
			if (charsetnr == MAGIC_BINARY_CHARSET_NR) {
				return @"binary";
			}
			return @"string";

		case MYSQL_TYPE_STRING:
			if (flags & ENUM_FLAG) {
				return @"enum";
			}
			if (flags & SET_FLAG) {
				return @"enum";
			}
			if ((flags & BINARY_FLAG) && charsetnr == MAGIC_BINARY_CHARSET_NR) {
				return @"binary";
			}
			return @"string";

		case MYSQL_TYPE_TINY_BLOB:   // should no appear over the wire
		case MYSQL_TYPE_MEDIUM_BLOB: // should no appear over the wire
		case MYSQL_TYPE_LONG_BLOB:   // should no appear over the wire
		case MYSQL_TYPE_BLOB:
		{
			if (charsetnr == MAGIC_BINARY_CHARSET_NR) {
				return @"blobdata";
			} else {
				return @"textdata";
			}
		}

		case MYSQL_TYPE_GEOMETRY:
			return @"geometry";

		default:
			return @"blobdata";
	}
}

@end
