Skip to content

Instantly share code, notes, and snippets.

@maca134
Created August 29, 2018 01:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maca134/26845631810b29b5e46cc8b5a0d5c366 to your computer and use it in GitHub Desktop.
Save maca134/26845631810b29b5e46cc8b5a0d5c366 to your computer and use it in GitHub Desktop.
SQLite table sql from POCO
namespace SQLiteTableBuilder
{
[Table]
public class Group
{
[Column(PrimaryKey = true, AutoIncrement = true)]
public int Id { get; set; }
[Column(Unique = true)]
public string Name { get; set; }
}
}
using System.Data.SQLite;
using System.IO;
namespace SQLiteTableBuilder
{
internal class Program
{
private static void Main(string[] args)
{
const string dbFilePath = "./app.db";
if (!File.Exists(dbFilePath)) SQLiteConnection.CreateFile(dbFilePath);
using (var dbConnection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;"))
{
dbConnection.Open();
var tables = new[]
{
new TableBuilder<Group>().ToSql(),
new TableBuilder<User>().ToSql()
};
foreach (var sql in tables)
{
var cmd = dbConnection.CreateCommand();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
namespace SQLiteTableBuilder
{
public enum CollateType
{
Binary,
Nocase,
Rtrim
}
public enum ForeignKeyOnClause
{
SetNull,
SetDefault,
Cascade,
Restrict,
NoAction
}
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public bool PrimaryKey { get; set; }
public bool AutoIncrement { get; set; }
public bool NotNull { get; set; }
public bool Unique { get; set; }
public string Default { get; set; }
public CollateType Collate { get; set; } = CollateType.Binary;
}
[AttributeUsage(AttributeTargets.Property)]
public class ForeignKeyAttribute : Attribute
{
public Type Entity { get; set; }
public string Property { get; set; }
public ForeignKeyOnClause Delete { get; set; } = ForeignKeyOnClause.Restrict;
public ForeignKeyOnClause Update { get; set; } = ForeignKeyOnClause.Restrict;
}
[AttributeUsage(AttributeTargets.Property)]
public class IndexAttribute : Attribute
{
public string[] OtherColumns { get; set; }
public bool Unique { get; set; }
}
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
public string TableName { get; set; }
public TableAttribute() { }
public TableAttribute(string tableName)
{
TableName = tableName;
}
}
public class TableBuilder<T> where T : new()
{
private enum DataType
{
Integer,
Text,
Blob,
Real,
Numeric
}
private class ForeignKey
{
public string TableName { get; set; }
public string ColumnName { get; set; }
public ForeignKeyOnClause Delete { get; set; }
public ForeignKeyOnClause Update { get; set; }
}
private class Column
{
public PropertyInfo PropertyInfo { get; }
public DataType DataType { get; }
public string Name { get; }
public bool PrimaryKey { get; }
public bool AutoIncrement { get; }
public bool NotNull { get; }
public bool Unique { get; }
public string Default { get; }
public CollateType Collate { get; }
public ForeignKey ForeignKey { get; }
public Column(PropertyInfo property)
{
PropertyInfo = property;
DataType = TypeToDataType(PropertyInfo.PropertyType);
var columnAttribute = (ColumnAttribute)property.GetCustomAttribute(typeof(ColumnAttribute));
PrimaryKey = columnAttribute.PrimaryKey;
AutoIncrement = columnAttribute.AutoIncrement;
NotNull = columnAttribute.NotNull;
Unique = columnAttribute.Unique;
Name = columnAttribute.Name ?? PropertyInfo.Name.ToLowerInvariant();
Collate = columnAttribute.Collate;
Default = columnAttribute.Default;
var fkAttribute = (ForeignKeyAttribute)property.GetCustomAttribute(typeof(ForeignKeyAttribute));
if (fkAttribute != null)
{
ForeignKey = new ForeignKey
{
TableName = GetTableName(fkAttribute.Entity),
ColumnName = fkAttribute.Property,
Delete = fkAttribute.Delete,
Update = fkAttribute.Update
};
}
}
}
private class Index
{
public Column[] Columns { get; }
public bool Unique { get; }
public Index(Column[] columns, bool unique)
{
Columns = columns;
Unique = unique;
}
}
private class Table
{
public string Name { get; }
public Column[] Columns { get; }
public Index[] Indices { get; }
public Table()
{
var type = typeof(T);
Name = GetTableName(type);
Columns = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(property => property.GetCustomAttribute(typeof(ColumnAttribute)) != null)
.Select(property => new Column(property)).ToArray();
Indices = Columns.Select(column =>
{
var index = (IndexAttribute)column.PropertyInfo.GetCustomAttribute(typeof(IndexAttribute));
if (index == null) return null;
var columns = new List<Column> { column };
columns.AddRange(index.OtherColumns.Select(name => Columns.First(col => col.Name == name.ToLowerInvariant())));
return new Index(columns.ToArray(), index.Unique);
}).Where(i => i != null).ToArray();
}
}
private readonly Table _table;
public TableBuilder()
{
_table = new Table();
}
public string ToSql()
{
var tableSql = new StringBuilder();
tableSql.Append("BEGIN;\n");
tableSql.Append("CREATE TABLE IF NOT EXISTS ");
tableSql.Append($"\"{_table.Name}\" (\n");
tableSql.Append(string.Join(",\n", _table.Columns.Select(column =>
{
var columnSql = new StringBuilder();
columnSql.Append($"\"{column.Name}\" {column.DataType.ToString().ToUpperInvariant()}");
if (
!column.PrimaryKey &&
!column.AutoIncrement &&
!column.NotNull &&
!column.Unique &&
column.Default == null &&
column.Collate == CollateType.Binary &&
column.ForeignKey == null
)
{
return columnSql.ToString();
}
if (column.PrimaryKey)
{
columnSql.Append(" PRIMARY KEY");
if (column.AutoIncrement)
columnSql.Append(" AUTOINCREMENT");
}
if (column.NotNull)
columnSql.Append(" NOT NULL");
if (column.Unique)
columnSql.Append($" CONSTRAINT \"U_{column.Name}\" UNIQUE");
if (column.Default != null)
columnSql.Append($" DEFAULT {column.Default}");
if (column.Collate != CollateType.Binary)
columnSql.Append($" COLLATE {column.Collate.ToString().ToUpperInvariant()}");
if (column.ForeignKey != null)
{
var tableName = column.ForeignKey.TableName;
columnSql.Append($" CONSTRAINT \"FK_{column.Name}_{tableName}\" REFERENCES \"{tableName}\"(\"{column.ForeignKey.ColumnName}\")");
columnSql.Append($" ON UPDATE {CamelCaseToSpaces(column.ForeignKey.Update.ToString()).ToUpperInvariant()}");
columnSql.Append($" ON DELETE {CamelCaseToSpaces(column.ForeignKey.Delete.ToString()).ToUpperInvariant()}");
}
return columnSql.ToString();
})));
tableSql.Append("\n);\n");
tableSql.Append(string.Join("", _table.Indices.Select(index =>
{
var indexSql = new StringBuilder();
indexSql.Append("CREATE ");
if (index.Unique)
indexSql.Append("UNIQUE ");
indexSql.Append($"INDEX IF NOT EXISTS \"I_{string.Join("_", index.Columns.Select(c => c.Name))}\"");
indexSql.Append($" ON \"{_table.Name}\" (\"{string.Join("\",\"", index.Columns.Select(c => c.Name))}\");\n");
return indexSql.ToString();
})));
tableSql.Append("COMMIT;\n");
return tableSql.ToString();
}
private static DataType TypeToDataType(Type clrType)
{
if (clrType == typeof(bool) ||
clrType == typeof(byte) ||
clrType == typeof(ushort) ||
clrType == typeof(sbyte) ||
clrType == typeof(short) ||
clrType == typeof(int) ||
clrType == typeof(uint) ||
clrType == typeof(long) ||
clrType == typeof(TimeSpan) ||
clrType == typeof(DateTimeOffset) ||
clrType.GetTypeInfo().IsEnum)
{
return DataType.Integer;
}
if (clrType == typeof(float) ||
clrType == typeof(double) ||
clrType == typeof(decimal))
{
return DataType.Real;
}
if (clrType == typeof(string) ||
clrType == typeof(StringBuilder) ||
clrType == typeof(Uri) ||
clrType == typeof(UriBuilder) ||
clrType == typeof(Guid))
{
return DataType.Text;
}
if (clrType == typeof(DateTime))
{
return DataType.Numeric;
}
if (clrType == typeof(byte[]))
{
return DataType.Blob;
}
throw new NotSupportedException("Don't know about " + clrType);
}
private static string GetTableName(MemberInfo table)
{
var tableAttr = (TableAttribute)table.GetCustomAttribute(typeof(TableAttribute));
if (tableAttr == null)
throw new Exception("class needs table attribute");
return tableAttr.TableName ?? table.Name.ToLowerInvariant();
}
private static string CamelCaseToSpaces(string input)
{
return Regex.Replace(input, @"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1");
}
}
}
namespace SQLiteTableBuilder
{
[Table(Name = "user")]
public class User
{
[Column(PrimaryKey = true, AutoIncrement = true)]
public int Id { get; set; }
[Column(NotNull = true), ForeignKey(Entity = typeof(Group), Property = nameof(Group.Id))]
public long GroupId { get; set; }
[Column, Index(Unique = true, OtherColumns = new[] { nameof(LastName) })]
public string FirstName { get; set; }
[Column]
public string LastName { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment