github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dtables/docs_table.go (about) 1 // Copyright 2021 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dtables 16 17 import ( 18 "fmt" 19 20 "github.com/dolthub/go-mysql-server/sql" 21 sqlTypes "github.com/dolthub/go-mysql-server/sql/types" 22 "github.com/dolthub/vitess/go/sqltypes" 23 24 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 25 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" 26 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" 27 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil" 28 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/writer" 29 "github.com/dolthub/dolt/go/store/hash" 30 ) 31 32 var DoltDocsSqlSchema sql.PrimaryKeySchema 33 var OldDoltDocsSqlSchema sql.PrimaryKeySchema 34 35 func init() { 36 DoltDocsSqlSchema, _ = sqlutil.FromDoltSchema("", doltdb.DocTableName, doltdb.DocsSchema) 37 OldDoltDocsSqlSchema, _ = sqlutil.FromDoltSchema("", doltdb.DocTableName, doltdb.OldDocsSchema) 38 } 39 40 var _ sql.Table = (*DocsTable)(nil) 41 var _ sql.UpdatableTable = (*DocsTable)(nil) 42 var _ sql.DeletableTable = (*DocsTable)(nil) 43 var _ sql.InsertableTable = (*DocsTable)(nil) 44 var _ sql.ReplaceableTable = (*DocsTable)(nil) 45 var _ sql.IndexAddressableTable = (*DocsTable)(nil) 46 47 // DocsTable is the system table that stores Dolt docs, such as LICENSE and README. 48 type DocsTable struct { 49 backingTable VersionableTable 50 } 51 52 func (dt *DocsTable) Name() string { 53 return doltdb.DocTableName 54 } 55 56 func (dt *DocsTable) String() string { 57 return doltdb.DocTableName 58 } 59 60 const defaultStringsLen = 16383 / 16 61 62 // Schema is a sql.Table interface function that gets the sql.Schema of the dolt_docs system table. 63 func (dt *DocsTable) Schema() sql.Schema { 64 return []*sql.Column{ 65 {Name: doltdb.DocPkColumnName, Type: sqlTypes.MustCreateString(sqltypes.VarChar, defaultStringsLen, sql.Collation_Default), Source: doltdb.DocTableName, PrimaryKey: true, Nullable: false}, 66 {Name: doltdb.DocTextColumnName, Type: sqlTypes.LongText, Source: doltdb.DocTableName, PrimaryKey: false}, 67 } 68 } 69 70 func (dt *DocsTable) Collation() sql.CollationID { 71 return sql.Collation_Default 72 } 73 74 // Partitions is a sql.Table interface function that returns a partition of the data. 75 func (dt *DocsTable) Partitions(context *sql.Context) (sql.PartitionIter, error) { 76 if dt.backingTable == nil { 77 // no backing table; return an empty iter. 78 return index.SinglePartitionIterFromNomsMap(nil), nil 79 } 80 return dt.backingTable.Partitions(context) 81 } 82 83 func (dt *DocsTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) { 84 if dt.backingTable == nil { 85 // no backing table; return an empty iter. 86 return sql.RowsToRowIter(), nil 87 } 88 89 return dt.backingTable.PartitionRows(context, partition) 90 } 91 92 // NewDocsTable creates a DocsTable 93 func NewDocsTable(_ *sql.Context, backingTable VersionableTable) sql.Table { 94 return &DocsTable{backingTable: backingTable} 95 } 96 97 // NewEmptyDocsTable creates a DocsTable 98 func NewEmptyDocsTable(_ *sql.Context) sql.Table { 99 return &DocsTable{} 100 } 101 102 // Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once 103 // for each row, followed by a call to Close() when all rows have been processed. 104 func (dt *DocsTable) Replacer(ctx *sql.Context) sql.RowReplacer { 105 return newDocsWriter(dt) 106 } 107 108 // Updater returns a RowUpdater for this table. The RowUpdater will have Update called once for each row to be 109 // updated, followed by a call to Close() when all rows have been processed. 110 func (dt *DocsTable) Updater(ctx *sql.Context) sql.RowUpdater { 111 return newDocsWriter(dt) 112 } 113 114 // Inserter returns an Inserter for this table. The Inserter will get one call to Insert() for each row to be 115 // inserted, and will end with a call to Close() to finalize the insert operation. 116 func (dt *DocsTable) Inserter(*sql.Context) sql.RowInserter { 117 return newDocsWriter(dt) 118 } 119 120 // Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted, 121 // and will end with a call to Close() to finalize the delete operation. 122 func (dt *DocsTable) Deleter(*sql.Context) sql.RowDeleter { 123 return newDocsWriter(dt) 124 } 125 126 func (dt *DocsTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) { 127 if dt.backingTable == nil { 128 return dt, nil 129 } 130 return dt.backingTable.LockedToRoot(ctx, root) 131 } 132 133 // IndexedAccess implements IndexAddressableTable, but DocsTables has no indexes. 134 // Thus, this should never be called. 135 func (dt *DocsTable) IndexedAccess(lookup sql.IndexLookup) sql.IndexedTable { 136 panic("Unreachable") 137 } 138 139 // GetIndexes implements IndexAddressableTable, but DocsTables has no indexes. 140 func (dt *DocsTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) { 141 return nil, nil 142 } 143 144 func (dt *DocsTable) PreciseMatch() bool { 145 return true 146 } 147 148 var _ sql.RowReplacer = (*docsWriter)(nil) 149 var _ sql.RowUpdater = (*docsWriter)(nil) 150 var _ sql.RowInserter = (*docsWriter)(nil) 151 var _ sql.RowDeleter = (*docsWriter)(nil) 152 153 type docsWriter struct { 154 it *DocsTable 155 errDuringStatementBegin error 156 prevHash *hash.Hash 157 tableWriter writer.TableWriter 158 } 159 160 func newDocsWriter(it *DocsTable) *docsWriter { 161 return &docsWriter{it, nil, nil, nil} 162 } 163 164 // Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process 165 // for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close 166 // is called. 167 func (iw *docsWriter) Insert(ctx *sql.Context, r sql.Row) error { 168 if err := iw.errDuringStatementBegin; err != nil { 169 return err 170 } 171 return iw.tableWriter.Insert(ctx, r) 172 } 173 174 // Update the given row. Provides both the old and new rows. 175 func (iw *docsWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error { 176 if err := iw.errDuringStatementBegin; err != nil { 177 return err 178 } 179 return iw.tableWriter.Update(ctx, old, new) 180 } 181 182 // Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for 183 // each row to process for the delete operation, which may involve many rows. After all rows have been processed, 184 // Close is called. 185 func (iw *docsWriter) Delete(ctx *sql.Context, r sql.Row) error { 186 if err := iw.errDuringStatementBegin; err != nil { 187 return err 188 } 189 return iw.tableWriter.Delete(ctx, r) 190 } 191 192 // StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data 193 // in some way that it may be returned to in the case of an error. 194 func (iw *docsWriter) StatementBegin(ctx *sql.Context) { 195 dbName := ctx.GetCurrentDatabase() 196 dSess := dsess.DSessFromSess(ctx.Session) 197 198 // TODO: this needs to use a revision qualified name 199 roots, _ := dSess.GetRoots(ctx, dbName) 200 dbState, ok, err := dSess.LookupDbState(ctx, dbName) 201 if err != nil { 202 iw.errDuringStatementBegin = err 203 return 204 } 205 if !ok { 206 iw.errDuringStatementBegin = fmt.Errorf("no root value found in session") 207 return 208 } 209 210 prevHash, err := roots.Working.HashOf() 211 if err != nil { 212 iw.errDuringStatementBegin = err 213 return 214 } 215 216 iw.prevHash = &prevHash 217 218 found, err := roots.Working.HasTable(ctx, doltdb.DocTableName) 219 220 if err != nil { 221 iw.errDuringStatementBegin = err 222 return 223 } 224 225 if !found { 226 // TODO: This is effectively a duplicate of the schema declaration above in a different format. 227 // We should find a way to not repeat ourselves. 228 newSchema := doltdb.DocsSchema 229 230 // underlying table doesn't exist. Record this, then create the table. 231 newRootValue, err := doltdb.CreateEmptyTable(ctx, roots.Working, doltdb.TableName{Name: doltdb.DocTableName}, newSchema) 232 if err != nil { 233 iw.errDuringStatementBegin = err 234 return 235 } 236 237 if dbState.WorkingSet() == nil { 238 iw.errDuringStatementBegin = doltdb.ErrOperationNotSupportedInDetachedHead 239 return 240 } 241 242 // We use WriteSession.SetWorkingSet instead of DoltSession.SetWorkingRoot because we want to avoid modifying the root 243 // until the end of the transaction, but we still want the WriteSession to be able to find the newly 244 // created table. 245 246 if ws := dbState.WriteSession(); ws != nil { 247 err = ws.SetWorkingSet(ctx, dbState.WorkingSet().WithWorkingRoot(newRootValue)) 248 if err != nil { 249 iw.errDuringStatementBegin = err 250 return 251 } 252 } 253 254 err = dSess.SetWorkingRoot(ctx, dbName, newRootValue) 255 if err != nil { 256 iw.errDuringStatementBegin = err 257 return 258 } 259 } 260 261 if ws := dbState.WriteSession(); ws != nil { 262 tableWriter, err := ws.GetTableWriter(ctx, doltdb.TableName{Name: doltdb.DocTableName}, dbName, dSess.SetWorkingRoot) 263 if err != nil { 264 iw.errDuringStatementBegin = err 265 return 266 } 267 iw.tableWriter = tableWriter 268 tableWriter.StatementBegin(ctx) 269 } 270 } 271 272 // DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning 273 // should be discarded. 274 func (iw *docsWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error { 275 if iw.tableWriter != nil { 276 return iw.tableWriter.DiscardChanges(ctx, errorEncountered) 277 } 278 return nil 279 } 280 281 // StatementComplete is called after the last operation of the statement, indicating that it has successfully completed. 282 // The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin. 283 func (iw *docsWriter) StatementComplete(ctx *sql.Context) error { 284 return iw.tableWriter.StatementComplete(ctx) 285 } 286 287 // Close finalizes the delete operation, persisting the result. 288 func (iw docsWriter) Close(ctx *sql.Context) error { 289 if iw.tableWriter != nil { 290 return iw.tableWriter.Close(ctx) 291 } 292 return nil 293 }