public IEnumerable <T> Get(ResourceQuery query, CancellationToken cancellationToken)
        {
            Debug.Assert(query.PageOffset.HasValue);
            Debug.Assert(query.PageSize.HasValue);

            var viewName = GetResourceName();
            var orderBy  = SqliteBuilder.OrderBySql(query);

            var sql     = SqliteBuilder.SelectSql(viewName, _members, query, out var hasWhere);
            var pageSql = sql + SqliteBuilder.PageSql(query, viewName, orderBy, hasWhere);

            var             db     = OpenConnection();
            IEnumerable <T> result = db.Query <T>(pageSql);

            if (!query.CountTotalRows)
            {
                return(result);
            }

            var countSql = SqliteBuilder.CountSql(sql, orderBy);
            var total    = db.QuerySingle <int>(countSql);

            query.TotalRows = total;

            return(result);
        }
        private void RebuildView(IDbConnection db, IDbTransaction t, IEnumerable <SqliteTableInfo> tableInfoList, int revision)
        {
            var viewName = GetResourceName();

            db.Execute(SqliteBuilder.DropViewSql(viewName), transaction: t);
            db.Execute(SqliteBuilder.CreateViewSql(viewName, _members, revision, tableInfoList), transaction: t);
        }
        public ResourceDataDistribution?GetResourceDataDistribution(int revision)
        {
            var viewName = GetResourceName();

            using var db = new SqliteConnection($"Data Source={FilePath}");
            db.Open();
            using var t = db.BeginTransaction();

            var tableInfoList = db.Query <SqliteTableInfo>(SqliteBuilder.GetTableInfo(), new { name = $"{viewName}%" }, t)
                                .Where(x => !x.name.Contains("_Search"))
                                .AsList() ?? throw new NullReferenceException();

            foreach (var tableInfo in tableInfoList)
            {
                var revisionString = Regex.Split(tableInfo.name, "([0-9])+$",
                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled)[1];

                if (!int.TryParse(revisionString, out var tableRevision) || tableRevision != revision)
                {
                    continue;
                }

                var count  = db.QuerySingle <long>($"SELECT COUNT(*) FROM \"{tableInfo.name}\"", transaction: t);
                var result = new ResourceDataDistribution {
                    Partition = tableInfo.name, RowCount = count
                };
                return(result);
            }

            return(default);
        private void CreateThroughTableRevisions(IDbConnection db, IDbTransaction t, int revision)
        {
            var viewName = GetResourceName();

            // Versioning:
            //
            // - Find the API version for the current resource revision
            // - For any embedded resource collections, find the revision for this API version

            var version = _changeLog.GetApiVersionForResourceAndRevision(viewName, revision);

            foreach (var member in _members)
            {
                if (!member.Type.ImplementsGeneric(typeof(IEnumerable <>)) || !member.Type.IsGenericType)
                {
                    continue; // not a collection
                }
                var arguments = member.Type.GetGenericArguments();
                var embeddedCollectionType = arguments[0];

                if (!typeof(IResource).IsAssignableFrom(embeddedCollectionType))
                {
                    continue; // not a resource collection
                }
                if (!_changeLog.TryGetResourceNameForType(embeddedCollectionType, out var embeddedViewName) || embeddedViewName == default)
                {
                    embeddedViewName = embeddedCollectionType.Name;
                }

                var embeddedViewRevision = _changeLog.GetRevisionForResourceAndApiVersion(embeddedViewName, version);
                var sql = SqliteBuilder.CreateThroughTableSql(viewName, revision, embeddedViewName, embeddedViewRevision);
                db.Execute(sql, transaction: t);
            }
        }
        private long GetNextSequence(int revision, IDbConnection db, IDbTransaction t)
        {
            var previous = revision == 1 ? 1 : revision - 1;

            var viewName = GetResourceName();

            // check current partition first
            var sequence = db.QuerySingleOrDefault <long?>(SqliteBuilder.GetMaxSequence(viewName, revision),
                                                           transaction: t);

            while (!sequence.HasValue)
            {
                sequence = db.QuerySingleOrDefault <long?>(
                    SqliteBuilder.GetMaxSequence(viewName, previous),
                    transaction: t);

                previous--;

                // account for multiple revisions before the first insertion
                if (previous == 0)
                {
                    sequence = 0;
                }
            }

            var nextSequence = sequence.GetValueOrDefault(0);

            nextSequence++;
            return(nextSequence);
        }
        private void Visit(IDbConnection db, IDbTransaction t)
        {
            var viewName = GetResourceName();

            var tableInfoList = db.Query <SqliteTableInfo>(SqliteBuilder.GetTableInfo(), new { name = $"{viewName}%" }, t)
                                .Where(x => !x.name.Contains("_Search"))
                                .AsList() ?? throw new NullReferenceException();

            int revision;

            if (tableInfoList.Count == 0)
            {
                revision = 1;
                Visit(db, t, revision, tableInfoList);
            }
            else
            {
                var tableInfo = tableInfoList[0];

                var revisionString = Regex.Split(tableInfo.name, "([0-9])+$",
                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled)[1];

                if (!int.TryParse(revisionString, out revision))
                {
                    throw new FormatException(_localizer.GetString("invalid revision number"));
                }

                if (_revision <= revision)
                {
                    return;
                }

                Visit(db, t, _revision, tableInfoList);
            }
        }
        private void InsertRecord(T resource, int revision, bool deleted, IDbConnection db, IDbTransaction t)
        {
            var sequence = GetNextSequence(revision, db, t);
            var viewName = GetResourceName();
            var hash     = SqliteBuilder.InsertSql(resource, viewName, _reads, _members, revision, sequence, deleted, out string sql);

            db.Execute(sql, hash, t);
        }
        public bool Exists(Guid id, bool includeDeleted, CancellationToken cancellationToken)
        {
            var db       = OpenConnection();
            var viewName = GetResourceName();
            var result   = db.QuerySingleOrDefault <int?>(SqliteBuilder.ExistsSql(viewName, includeDeleted), new { Id = id });

            return(result != default);
        }
 public bool TryGetById(Guid id, out T?resource, out bool error, List <string>?fields, bool includeDeleted, CancellationToken cancellationToken)
 {
     try
     {
         var db       = OpenConnection();
         var viewName = GetResourceName();
         var result   = db.QuerySingleOrDefault <T?>(SqliteBuilder.GetByIdSql(viewName, _members, fields, includeDeleted), new { Id = id });
         resource = result;
         error    = false;
         return(resource != default);
     }
     catch (Exception e)
     {
         _logger.LogError(ErrorEvents.ErrorRetrievingResource, e, _localizer.GetString("Error retrieving resource from SQLite"));
         resource = default;
         error    = true;
         return(false);
     }
 }
        private void CreateTableRevision(IDbConnection db, IDbTransaction t, int revision)
        {
            var viewName = GetResourceName();

            {
                var sql = SqliteBuilder.CreateTableSql(viewName, _members, revision, false);
                db.Execute(sql, transaction: t);
            }

            if (SupportsSearch)
            {
                var sql = SqliteBuilder.AfterInsertTriggerSql(viewName, _members, revision);
                db.Execute(sql, transaction: t);
            }

            foreach (var member in _members.GetValueTypeFields())
            {
                if (member.Name.Equals(nameof(IResource.Id)))
                {
                    IndexMember(db, t, revision, member, true);
                }

                if (member.HasAttribute <IndexAttribute>())
                {
                    IndexMember(db, t, revision, member, false);
                }
            }

            // FIXME: deal with user field name collisions
            {
                var sql = SqliteBuilder.CreateIndexSql(GetResourceName(), "IsDeleted", revision, false);
                db.Execute(sql, transaction: t);
            }

            if (SupportsSearch)
            {
                var sql = SqliteBuilder.CreateTableSql(viewName, _members, revision, true);
                db.Execute(sql, transaction: t);
            }
        }
        private void IndexMember(IDbConnection db, IDbTransaction t, int revision, AccessorMember member, bool unique)
        {
            var sql = SqliteBuilder.CreateIndexSql(GetResourceName(), member.Name, revision, unique);

            db.Execute(sql, transaction: t);
        }