Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 135 additions & 1 deletion src/SQLite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -833,8 +833,31 @@ public int DropTable<
/// </param>
public int DropTable (TableMapping map)
{
var count = 0;

if (map.FullTextTableName != null && map.FullTextColumns.Length > 0) {
// Drop full text index before content table
var queryFullText = string.Format ("DROP TABLE IF EXISTS \"{0}\"", map.FullTextTableName);
count += Execute (queryFullText);

// Drop full text index triggers
var queryFullTextTriggerBeforeUpdate = string.Format ("DROP TRIGGER IF EXISTS \"{0}_t_bu\"", map.FullTextTableName);
count += Execute (queryFullTextTriggerBeforeUpdate);

var queryFullTextTriggerBeforeDelete = string.Format ("DROP TRIGGER IF EXISTS \"{0}_t_bd\"", map.FullTextTableName);
count += Execute (queryFullTextTriggerBeforeDelete);

var queryFullTextTriggerAfterUpdate = string.Format ("DROP TRIGGER IF EXISTS \"{0}_t_au\"", map.FullTextTableName);
count += Execute (queryFullTextTriggerAfterUpdate);

var queryFullTextTriggerAfterInsert = string.Format ("DROP TRIGGER IF EXISTS \"{0}_t_ai\"", map.FullTextTableName);
count += Execute (queryFullTextTriggerAfterInsert);
}

var query = string.Format ("drop table if exists \"{0}\"", map.TableName);
return Execute (query);
count += Execute (query);

return count;
}

/// <summary>
Expand Down Expand Up @@ -941,6 +964,39 @@ public CreateTableResult CreateTable (
CreateIndex (indexName, index.TableName, columns, index.Unique);
}

// Create full text indexes
if (map.FullTextTableName != null) {
if (map.FullTextColumns.Length == 0) {
throw new Exception ("Must have at least one full text index column to specify a full text index for the table");
}

var fullTextColsNames = map.FullTextColumns.Select (c => c.Name);
// Only support FTS4 because content= options to link an FTS table to external tables is not supported in FTS3
var queryFullText = "CREATE VIRTUAL TABLE IF NOT EXISTS \"" + map.FullTextTableName + "\" USING fts4(content=\"" + map.TableName + "\", ";
queryFullText += string.Join (",\n", fullTextColsNames.ToArray ());
queryFullText += ", tokenize=" + map.FullTextTableTokenizer;
queryFullText += ")";

Execute (queryFullText);

// Create triggers to keep full text index in sync with content table
var deleteOldSql = "DELETE FROM \"" + map.FullTextTableName + "\" WHERE docid=old." + map.PK.Name + ";";
var insertNewSql = "INSERT INTO \"" + map.FullTextTableName + "\"(docid, " + string.Join (", ", fullTextColsNames);
insertNewSql += ") VALUES (new." + map.PK.Name + ", new." + string.Join (", new.", fullTextColsNames) + ");";

var beforeUpdateSql = "CREATE TRIGGER IF NOT EXISTS \"" + map.FullTextTableName + "_t_bu\" BEFORE UPDATE ON \"" + map.TableName + "\" BEGIN\n" + deleteOldSql + "\nEND;";
Execute (beforeUpdateSql);

var beforeDeleteSql = "CREATE TRIGGER IF NOT EXISTS \"" + map.FullTextTableName + "_t_bd\" BEFORE DELETE ON \"" + map.TableName + "\" BEGIN\n" + deleteOldSql + "\nEND;";
Execute (beforeDeleteSql);

var afterUpdateSql = "CREATE TRIGGER IF NOT EXISTS \"" + map.FullTextTableName + "_t_au\" AFTER UPDATE ON \"" + map.TableName + "\" BEGIN\n" + insertNewSql + "\nEND;";
Execute (afterUpdateSql);

var afterInsertSql = "CREATE TRIGGER IF NOT EXISTS \"" + map.FullTextTableName + "_t_ai\" AFTER INSERT ON \"" + map.TableName + "\" BEGIN\n" + insertNewSql + "\nEND;";
Execute (afterInsertSql);
}

return result;
}

Expand Down Expand Up @@ -2917,6 +2973,45 @@ public class StoreAsTextAttribute : Attribute
{
}

public enum FullTextSearchModule
{
FullTextSearch3 = 0,
FullTextSearch4 = 1
}

/// <summary>
/// Attribute to specify a full-text search table created from a subset of the
/// table columns. Name is the name of the FTS table, and Tokenizer is the tokenizer
/// to use, which can be one of the built-in tokenizers: "simple", "porter", "unicode61", or (possibly) "icu".
/// </summary>
[AttributeUsage (AttributeTargets.Class)]
public class FullTextTableAttribute : Attribute
{
public string Name { get; set; }
public string Tokenizer { get; set; }
public FullTextSearchModule Module { get; set; } = FullTextSearchModule.FullTextSearch4;

public FullTextTableAttribute ()
{
}

public FullTextTableAttribute (string name)
{
Name = name;
}

public FullTextTableAttribute (string name, string tokenizer)
{
Name = name;
Tokenizer = tokenizer;
}
}

[AttributeUsage (AttributeTargets.Property)]
public class FullTextIndexedAttribute : Attribute
{
}

public class TableMapping
{
#if NET8_0_OR_GREATER
Expand All @@ -2934,13 +3029,18 @@ public class TableMapping

public string GetByPrimaryKeySql { get; private set; }

public string FullTextTableName { get; private set; }

public string FullTextTableTokenizer { get; private set; }

public CreateFlags CreateFlags { get; private set; }

internal MapMethod Method { get; private set; } = MapMethod.ByName;

readonly Column _autoPk;
readonly Column[] _insertColumns;
readonly Column[] _insertOrReplaceColumns;
Column[] _fullTextColumns;

public TableMapping (
#if NET8_0_OR_GREATER
Expand Down Expand Up @@ -2969,6 +3069,23 @@ public TableMapping (
TableName = (tableAttr != null && !string.IsNullOrEmpty (tableAttr.Name)) ? tableAttr.Name : MappedType.Name;
WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false;

#if ENABLE_IL2CPP
var fullTextTableAttr = typeInfo.GetCustomAttribute<FullTextTableAttribute> ();
#elif NET8_0_OR_GREATER
var fullTextTableAttr = type.GetCustomAttributes<FullTextTableAttribute> ().FirstOrDefault ();
#else
var fullTextTableAttr =
typeInfo.CustomAttributes
.Where (x => x.AttributeType == typeof (FullTextTableAttribute))
.Select (x => (FullTextTableAttribute)Orm.InflateAttribute (x))
.FirstOrDefault ();
#endif

if (fullTextTableAttr != null) {
FullTextTableName = fullTextTableAttr.Name != null ? fullTextTableAttr.Name : MappedType.Name + "_FT";
FullTextTableTokenizer = fullTextTableAttr.Tokenizer != null ? fullTextTableAttr.Tokenizer : "unicode61"; // The default "simple" tokenizer only supports ASCII so default to "unicode61" instead
}

var members = GetPublicMembers(type);
var cols = new List<Column>(members.Count);
foreach(var m in members)
Expand Down Expand Up @@ -3075,6 +3192,15 @@ public Column[] InsertOrReplaceColumns {
}
}

public Column[] FullTextColumns {
get {
if (_fullTextColumns == null) {
_fullTextColumns = Columns.Where (c => c.IsInFullTextIndex).ToArray ();
}
return _fullTextColumns;
}
}

public Column FindColumnWithPropertyName (string propertyName)
{
var exact = Columns.FirstOrDefault (c => c.PropertyName == propertyName);
Expand Down Expand Up @@ -3111,6 +3237,8 @@ public class Column

public IEnumerable<IndexedAttribute> Indices { get; set; }

public bool IsInFullTextIndex { get; private set; }

public bool IsNullable { get; private set; }

public int? MaxStringLength { get; private set; }
Expand Down Expand Up @@ -3151,6 +3279,7 @@ public Column (MemberInfo member, CreateFlags createFlags = CreateFlags.None)
) {
Indices = new IndexedAttribute[] { new IndexedAttribute () };
}
IsInFullTextIndex = Orm.IsInFullTextIndex (member);
IsNullable = !(IsPK || Orm.IsMarkedNotNull (member));
MaxStringLength = Orm.MaxStringLength (member);

Expand Down Expand Up @@ -3454,6 +3583,11 @@ public static bool IsMarkedNotNull (MemberInfo p)
{
return p.CustomAttributes.Any (x => x.AttributeType == typeof (NotNullAttribute));
}

public static bool IsInFullTextIndex (MemberInfo p)
{
return p.CustomAttributes.Any (x => x.AttributeType == typeof (FullTextIndexedAttribute));
}
}

public partial class SQLiteCommand
Expand Down
Loading