Skip to content

Commit 72b69db

Browse files
committed
Add CSV scripted importer that imports a SQLiteAsset
1 parent 709e19f commit 72b69db

10 files changed

+425
-6
lines changed

Editor/Csv.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/Csv/SQLiteAssetCsvImporter.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2025 Gil Barbosa Reis
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
using System.IO;
23+
using SQLite.Csv;
24+
#if UNITY_2020_2_OR_NEWER
25+
using UnityEditor.AssetImporters;
26+
#else
27+
using UnityEditor.Experimental.AssetImporters;
28+
#endif
29+
using UnityEngine;
30+
31+
namespace SQLite.Editor.Csv
32+
{
33+
[ScriptedImporter(0, null, new[] { "csv" })]
34+
public class SQLiteAssetCsvImporter : ScriptedImporter
35+
{
36+
[Header("SQLite asset options")]
37+
[Tooltip("Name of the table that will be created for holding the CSV data inside the database.")]
38+
[SerializeField] private string _tableName = "data";
39+
40+
[Tooltip("Flags controlling how the SQLite connection should be opened. 'ReadWrite' and 'Create' flags will be ignored, since SQLite assets are read-only.")]
41+
[SerializeField] private SQLiteOpenFlags _openFlags = SQLiteOpenFlags.ReadOnly;
42+
43+
[Tooltip("Whether to store DateTime properties as ticks (true) or strings (false).")]
44+
[SerializeField] private bool _storeDateTimeAsTicks = true;
45+
46+
[Tooltip("Name of the file created for the database inside Streaming Assets folder during builds.\n\n"
47+
+ "If empty, the database bytes will be stored in the asset itself.\n\n"
48+
+ "Loading databases from Streaming Assets is not supported in Android and WebGL platforms.")]
49+
[SerializeField] private string _streamingAssetsPath;
50+
51+
52+
[Header("CSV options")]
53+
[Tooltip("Which separator character will be used when parsing the CSV file.")]
54+
[SerializeField] private CsvReader.SeparatorChar _CSVSeparator = CsvReader.SeparatorChar.Comma;
55+
56+
[Tooltip("If true, the original CSV file will also be imported as a TextAsset")]
57+
[SerializeField] private bool _importCSVTextAsset = false;
58+
59+
[Header("Additional SQL")]
60+
[Tooltip("SQL script that will be run before reading CSV data. Use this for configuring the generated database using PRAGMAs like 'page_size'.")]
61+
[SerializeField, Multiline] private string _SQLBeforeReadingCSV = "";
62+
63+
[Tooltip("SQL script that will be run after reading CSV data. Use this for changing the table's schema, creating indices, etc.")]
64+
[SerializeField, Multiline] private string _SQLAfterReadingCSV = "";
65+
66+
public override void OnImportAsset(AssetImportContext ctx)
67+
{
68+
SQLiteAsset asset;
69+
using (var tempDb = new SQLiteConnection(""))
70+
using (var file = File.OpenRead(assetPath))
71+
using (var stream = new StreamReader(file))
72+
{
73+
if (!string.IsNullOrWhiteSpace(_SQLBeforeReadingCSV))
74+
{
75+
tempDb.Execute(_SQLBeforeReadingCSV);
76+
}
77+
tempDb.ImportCsvToTable(_tableName, stream, _CSVSeparator);
78+
if (!string.IsNullOrWhiteSpace(_SQLAfterReadingCSV))
79+
{
80+
tempDb.Execute(_SQLAfterReadingCSV);
81+
}
82+
83+
asset = tempDb.SerializeToAsset(null, _openFlags, _storeDateTimeAsTicks, _streamingAssetsPath);
84+
}
85+
ctx.AddObjectToAsset("sqlite", asset);
86+
ctx.SetMainObject(asset);
87+
88+
if (_importCSVTextAsset)
89+
{
90+
var textAsset = new TextAsset(File.ReadAllText(assetPath))
91+
{
92+
name = $"{Path.GetFileNameWithoutExtension(assetPath)}",
93+
};
94+
ctx.AddObjectToAsset("text", textAsset);
95+
}
96+
}
97+
}
98+
}

Editor/Csv/SQLiteAssetCsvImporter.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,21 @@ This package provides the excelent [SQLite-net](https://github.com/praeclarum/sq
99
+ Both synchronous and asynchronous APIs are available
1010
+ `SQLiteConnection.Serialize` extension method for serializing a database to `byte[]` (reference: [SQLite Serialization](https://www.sqlite.org/c3ref/serialize.html)).
1111
+ `SQLiteConnection.Deserialize` extension method for deserializing memory (`byte[]`, `NativeArray<byte>` or `ReadOnlySpan<byte>`) into an open database (reference: [SQLite Deserialization](https://www.sqlite.org/c3ref/deserialize.html)).
12+
+ `SQLiteConnection.ImportCsvToTable` extension method for importing a CSV text stream as a new table inside the database.
1213
- [SQLite3 Multiple Ciphers 2.1.3](https://github.com/utelle/SQLite3MultipleCiphers/releases/tag/v2.1.3) (based on [SQLite 3.50.1](https://sqlite.org/releaselog/3_50_1.html))
1314
+ Supports encrypted databases
1415
+ Enabled modules: [R\*Tree](https://sqlite.org/rtree.html), [Geopoly](https://sqlite.org/geopoly.html), [FTS5](https://sqlite.org/fts5.html), [Built-In Math Functions](https://www.sqlite.org/lang_mathfunc.html)
1516
+ Supports Windows, Linux, macOS, WebGL, Android, iOS, tvOS and visionOS platforms
1617
+ Supports persisting data in WebGL builds by using a [custom VFS backed by Indexed DB](https://github.com/gilzoide/idbvfs).
1718
- [SQLiteAsset](Runtime/SQLiteAsset.cs): read-only SQLite database Unity assets.
1819
+ Files with the extensions ".sqlite", ".sqlite2" and ".sqlite3" will be imported as SQLite database assets.
20+
+ ".csv" files can be imported as SQLite database assets by changing the importer to `SQLite.Editor.Csv.SQLiteAssetCsvImporter` in the Inspector.
1921
+ Use the `CreateConnection()` method for connecting to the database provided by the asset.
2022
Make sure to `Dispose()` of any connections you create.
2123
+ SQLite assets may be loaded from Streaming Assets folder or from memory, depending on the value of their "Streaming Assets Path" property.
2224
+ **Warning**: Android and WebGL platforms don't support loading SQLite databases from Streaming Assets and will always load them in memory.
2325
+ `SQLiteConnection.SerializeToAsset` extension method for serializing a database to an instance of `SQLiteAsset`.
2426

25-
## Optional packages
26-
- [SQLite Asset](https://github.com/gilzoide/unity-sqlite-asset): read-only SQLite database assets for Unity with scripted importer for ".sqlite", ".sqlite2" and ".sqlite3" files
27-
- [SQLite Asset - CSV](https://github.com/gilzoide/unity-sqlite-asset-csv): easily import ".csv" files as read-only SQLite database assets
28-
29-
3027
## How to install
3128
Either:
3229
- Use the [openupm registry](https://openupm.com/) and install this package using the [openupm-cli](https://github.com/openupm/openupm-cli):

Runtime/Csv.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Csv/CsvException.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2025 Gil Barbosa Reis
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
using System;
23+
24+
namespace SQLite.Csv
25+
{
26+
public class CsvException : Exception
27+
{
28+
public CsvException(string message) : base(message) {}
29+
}
30+
}

Runtime/Csv/CsvException.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Csv/CsvReader.cs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright (c) 2025 Gil Barbosa Reis
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
using System;
23+
using System.Collections.Generic;
24+
using System.IO;
25+
using System.Text;
26+
27+
namespace SQLite.Csv
28+
{
29+
public static class CsvReader
30+
{
31+
public enum SeparatorChar
32+
{
33+
Comma,
34+
Semicolon,
35+
Tabs,
36+
}
37+
38+
/// <summary>
39+
/// Parse a stream of CSV-formatted data.
40+
/// </summary>
41+
/// <param name="stream">Stream of CSV-formatted data.</param>
42+
/// <param name="separator">Character used for separating fields.</param>
43+
/// <param name="maxFieldSize">Maximum field size allowed.</param>
44+
/// <returns>
45+
/// The enumeration returns each field's contents, even if it is empty.
46+
/// <see langword="null"/> is returned at the end of the lines, meaning a new row will start next.
47+
/// Empty lines are ignored and will not be enumerated.
48+
/// </returns>
49+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="stream"/> is null.</exception>
50+
/// <exception cref="CsvException">Thrown if any field size is greater than <paramref name="maxFieldSize"/>.</exception>
51+
public static IEnumerable<string> ParseStream(TextReader stream, SeparatorChar separator = SeparatorChar.Comma, int maxFieldSize = int.MaxValue)
52+
{
53+
if (stream == null)
54+
{
55+
throw new ArgumentNullException(nameof(stream));
56+
}
57+
58+
SkipEmptyLines(stream);
59+
if (stream.Peek() < 0)
60+
{
61+
yield break;
62+
}
63+
64+
bool insideQuotes = false;
65+
var stringBuilder = new StringBuilder();
66+
while (true)
67+
{
68+
int c = stream.Read();
69+
switch (c)
70+
{
71+
case '\r':
72+
if (!insideQuotes && stream.Peek() == '\n')
73+
{
74+
stream.Read();
75+
goto case '\n';
76+
}
77+
else
78+
{
79+
goto default;
80+
}
81+
82+
case '\n':
83+
if (!insideQuotes)
84+
{
85+
yield return stringBuilder.ToString();
86+
stringBuilder.Clear();
87+
yield return null;
88+
89+
SkipEmptyLines(stream);
90+
if (stream.Peek() < 0)
91+
{
92+
yield break;
93+
}
94+
}
95+
else
96+
{
97+
goto default;
98+
}
99+
break;
100+
101+
case ',':
102+
if (!insideQuotes && separator == SeparatorChar.Comma)
103+
{
104+
yield return stringBuilder.ToString();
105+
stringBuilder.Clear();
106+
}
107+
else
108+
{
109+
goto default;
110+
}
111+
break;
112+
113+
case ';':
114+
if (!insideQuotes && separator == SeparatorChar.Semicolon)
115+
{
116+
yield return stringBuilder.ToString();
117+
stringBuilder.Clear();
118+
}
119+
else
120+
{
121+
goto default;
122+
}
123+
break;
124+
125+
case '\t':
126+
if (!insideQuotes && separator == SeparatorChar.Tabs)
127+
{
128+
yield return stringBuilder.ToString();
129+
stringBuilder.Clear();
130+
}
131+
else
132+
{
133+
goto default;
134+
}
135+
break;
136+
137+
case '"':
138+
if (insideQuotes && stream.Peek() == '"')
139+
{
140+
stream.Read();
141+
goto default;
142+
}
143+
else
144+
{
145+
insideQuotes = !insideQuotes;
146+
}
147+
break;
148+
149+
case < 0:
150+
yield return stringBuilder.ToString();
151+
yield return null;
152+
yield break;
153+
154+
default:
155+
if (stringBuilder.Length >= maxFieldSize)
156+
{
157+
throw new CsvException("Field size is greater than maximum allowed size.");
158+
}
159+
stringBuilder.Append((char) c);
160+
break;
161+
}
162+
}
163+
}
164+
165+
private static void SkipEmptyLines(TextReader streamReader)
166+
{
167+
while (true)
168+
{
169+
int c = streamReader.Peek();
170+
switch (c)
171+
{
172+
case '\n':
173+
case '\r':
174+
streamReader.Read();
175+
continue;
176+
177+
default:
178+
return;
179+
}
180+
}
181+
}
182+
}
183+
}

Runtime/Csv/CsvReader.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)