github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/sqle/dtables/commit_diff_table.go (about) 1 // Copyright 2020 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 "context" 19 "errors" 20 "fmt" 21 "io" 22 "strings" 23 "sync" 24 25 "github.com/dolthub/go-mysql-server/sql" 26 "github.com/dolthub/go-mysql-server/sql/expression" 27 "github.com/dolthub/go-mysql-server/sql/parse" 28 29 "github.com/dolthub/dolt/go/libraries/doltcore/diff" 30 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 31 "github.com/dolthub/dolt/go/libraries/doltcore/rowconv" 32 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 33 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil" 34 "github.com/dolthub/dolt/go/store/types" 35 ) 36 37 var ErrExactlyOneToCommit = errors.New("dolt_commit_diff_* tables must be filtered to a single 'to_commit'") 38 var ErrExactlyOneFromCommit = errors.New("dolt_commit_diff_* tables must be filtered to a single 'from_commit'") 39 40 var _ sql.Table = (*CommitDiffTable)(nil) 41 var _ sql.FilteredTable = (*CommitDiffTable)(nil) 42 43 type CommitDiffTable struct { 44 name string 45 ddb *doltdb.DoltDB 46 ss *schema.SuperSchema 47 joiner *rowconv.Joiner 48 sqlSch sql.Schema 49 workingRoot *doltdb.RootValue 50 fromCommitFilter *expression.Equals 51 toCommitFilter *expression.Equals 52 requiredFilterErr error 53 } 54 55 func NewCommitDiffTable(ctx *sql.Context, tblName string, ddb *doltdb.DoltDB, root *doltdb.RootValue) (sql.Table, error) { 56 diffTblName := doltdb.DoltCommitDiffTablePrefix + tblName 57 58 ss, err := calcSuperDuperSchema(ctx, ddb, root, tblName) 59 60 if err != nil { 61 return nil, err 62 } 63 64 _ = ss.AddColumn(schema.NewColumn("commit", schema.DiffCommitTag, types.StringKind, false)) 65 _ = ss.AddColumn(schema.NewColumn("commit_date", schema.DiffCommitDateTag, types.TimestampKind, false)) 66 67 sch, err := ss.GenerateSchema() 68 69 if err != nil { 70 return nil, err 71 } 72 73 if sch.GetAllCols().Size() <= 1 { 74 return nil, sql.ErrTableNotFound.New(diffTblName) 75 } 76 77 j, err := rowconv.NewJoiner( 78 []rowconv.NamedSchema{{Name: diff.To, Sch: sch}, {Name: diff.From, Sch: sch}}, 79 map[string]rowconv.ColNamingFunc{ 80 diff.To: toNamer, 81 diff.From: fromNamer, 82 }) 83 84 if err != nil { 85 return nil, err 86 } 87 88 sqlSch, err := sqlutil.FromDoltSchema(diffTblName, j.GetSchema()) 89 90 if err != nil { 91 return nil, err 92 } 93 94 // parses to literal, no need to pass through analyzer 95 defaultVal, err := parse.StringToColumnDefaultValue(ctx, fmt.Sprintf(`"%s"`, diffTypeModified)) 96 if err != nil { 97 return nil, err 98 } 99 100 sqlSch = append(sqlSch, &sql.Column{ 101 Name: diffTypeColName, 102 Type: sql.Text, 103 Default: defaultVal, 104 Nullable: false, 105 Source: diffTblName, 106 }) 107 108 return &CommitDiffTable{ 109 name: tblName, 110 ddb: ddb, 111 workingRoot: root, 112 ss: ss, 113 joiner: j, 114 sqlSch: sqlSch, 115 }, nil 116 } 117 118 func calcSuperDuperSchema(ctx context.Context, ddb *doltdb.DoltDB, working *doltdb.RootValue, tblName string) (*schema.SuperSchema, error) { 119 refs, err := ddb.GetHeadRefs(ctx) 120 121 if err != nil { 122 return nil, err 123 } 124 125 var superSchemas []*schema.SuperSchema 126 ss, found, err := working.GetSuperSchema(ctx, tblName) 127 128 if err != nil { 129 return nil, err 130 } 131 132 if found { 133 superSchemas = append(superSchemas, ss) 134 } 135 136 for _, ref := range refs { 137 cm, err := ddb.ResolveCommitRef(ctx, ref) 138 139 if err != nil { 140 return nil, err 141 } 142 143 cmRoot, err := cm.GetRootValue() 144 145 if err != nil { 146 return nil, err 147 } 148 149 ss, found, err = cmRoot.GetSuperSchema(ctx, tblName) 150 151 if err != nil { 152 return nil, err 153 } 154 155 if found { 156 superSchemas = append(superSchemas, ss) 157 } 158 } 159 160 if len(superSchemas) == 0 { 161 return nil, sql.ErrTableNotFound.New(tblName) 162 } 163 164 superDuperSchema, err := schema.SuperSchemaUnion(superSchemas...) 165 166 if err != nil { 167 return nil, err 168 } 169 170 return superDuperSchema, nil 171 } 172 173 func (dt *CommitDiffTable) Name() string { 174 return doltdb.DoltCommitDiffTablePrefix + dt.name 175 } 176 177 func (dt *CommitDiffTable) String() string { 178 return doltdb.DoltCommitDiffTablePrefix + dt.name 179 } 180 181 func (dt *CommitDiffTable) Schema() sql.Schema { 182 return dt.sqlSch 183 } 184 185 type SliceOfPartitionsItr struct { 186 partitions []sql.Partition 187 i int 188 mu *sync.Mutex 189 } 190 191 func NewSliceOfPartitionsItr(partitions []sql.Partition) *SliceOfPartitionsItr { 192 return &SliceOfPartitionsItr{ 193 partitions: partitions, 194 mu: &sync.Mutex{}, 195 } 196 } 197 198 func (itr *SliceOfPartitionsItr) Next() (sql.Partition, error) { 199 itr.mu.Lock() 200 defer itr.mu.Unlock() 201 202 if itr.i >= len(itr.partitions) { 203 return nil, io.EOF 204 } 205 206 next := itr.partitions[itr.i] 207 itr.i++ 208 209 return next, nil 210 } 211 212 func (itr *SliceOfPartitionsItr) Close(*sql.Context) error { 213 return nil 214 } 215 216 func (dt *CommitDiffTable) Partitions(ctx *sql.Context) (sql.PartitionIter, error) { 217 if dt.requiredFilterErr != nil { 218 return nil, fmt.Errorf("error querying table %s: %w", dt.Name(), dt.requiredFilterErr) 219 } else if dt.toCommitFilter == nil { 220 return nil, fmt.Errorf("error querying table %s: %w", dt.Name(), ErrExactlyOneToCommit) 221 } else if dt.fromCommitFilter == nil { 222 return nil, fmt.Errorf("error querying table %s: %w", dt.Name(), ErrExactlyOneFromCommit) 223 } 224 225 toRoot, toName, toDate, err := dt.rootValForFilter(ctx, dt.toCommitFilter) 226 227 if err != nil { 228 return nil, err 229 } 230 231 fromRoot, fromName, fromDate, err := dt.rootValForFilter(ctx, dt.fromCommitFilter) 232 233 if err != nil { 234 return nil, err 235 } 236 237 toTable, _, err := toRoot.GetTable(ctx, dt.name) 238 239 if err != nil { 240 return nil, err 241 } 242 243 fromTable, _, err := fromRoot.GetTable(ctx, dt.name) 244 245 if err != nil { 246 return nil, err 247 } 248 249 return NewSliceOfPartitionsItr([]sql.Partition{diffPartition{ 250 to: toTable, 251 from: fromTable, 252 toName: toName, 253 fromName: fromName, 254 toDate: toDate, 255 fromDate: fromDate, 256 }}), nil 257 } 258 259 func (dt *CommitDiffTable) rootValForFilter(ctx *sql.Context, eqFilter *expression.Equals) (*doltdb.RootValue, string, *types.Timestamp, error) { 260 gf, nonGF := eqFilter.Left(), eqFilter.Right() 261 if _, ok := gf.(*expression.GetField); !ok { 262 nonGF, gf = eqFilter.Left(), eqFilter.Right() 263 } 264 265 val, err := nonGF.Eval(ctx, nil) 266 267 if err != nil { 268 return nil, "", nil, err 269 } 270 271 hashStr, ok := val.(string) 272 273 if !ok { 274 return nil, "", nil, fmt.Errorf("received '%v' when expecting commit hash string", val) 275 } 276 277 var root *doltdb.RootValue 278 var commitTime *types.Timestamp 279 if strings.ToLower(hashStr) == "working" { 280 root = dt.workingRoot 281 } else { 282 cs, err := doltdb.NewCommitSpec(hashStr) 283 284 if err != nil { 285 return nil, "", nil, err 286 } 287 288 cm, err := dt.ddb.Resolve(ctx, cs, nil) 289 290 if err != nil { 291 return nil, "", nil, err 292 } 293 294 root, err = cm.GetRootValue() 295 296 if err != nil { 297 return nil, "", nil, err 298 } 299 300 meta, err := cm.GetCommitMeta() 301 302 if err != nil { 303 return nil, "", nil, err 304 } 305 306 t := meta.Time() 307 commitTime = (*types.Timestamp)(&t) 308 } 309 310 return root, hashStr, commitTime, nil 311 } 312 313 // HandledFilters returns the list of filters that will be handled by the table itself 314 func (dt *CommitDiffTable) HandledFilters(filters []sql.Expression) []sql.Expression { 315 var commitFilters []sql.Expression 316 for _, filter := range filters { 317 isCommitFilter := false 318 319 if eqFilter, isEquality := filter.(*expression.Equals); isEquality { 320 for _, e := range []sql.Expression{eqFilter.Left(), eqFilter.Right()} { 321 if val, ok := e.(*expression.GetField); ok { 322 switch strings.ToLower(val.Name()) { 323 case toCommit: 324 if dt.toCommitFilter != nil { 325 dt.requiredFilterErr = ErrExactlyOneToCommit 326 } 327 328 isCommitFilter = true 329 dt.toCommitFilter = eqFilter 330 case fromCommit: 331 if dt.fromCommitFilter != nil { 332 dt.requiredFilterErr = ErrExactlyOneFromCommit 333 } 334 335 isCommitFilter = true 336 dt.fromCommitFilter = eqFilter 337 } 338 } 339 } 340 } 341 342 if isCommitFilter { 343 commitFilters = append(commitFilters, filter) 344 } 345 } 346 347 return commitFilters 348 } 349 350 // Filters returns the list of filters that are applied to this table. 351 func (dt *CommitDiffTable) Filters() []sql.Expression { 352 if dt.toCommitFilter == nil || dt.fromCommitFilter == nil { 353 return nil 354 } 355 356 return []sql.Expression{dt.toCommitFilter, dt.fromCommitFilter} 357 } 358 359 // WithFilters returns a new sql.Table instance with the filters applied 360 func (dt *CommitDiffTable) WithFilters(ctx *sql.Context, filters []sql.Expression) sql.Table { 361 return dt 362 } 363 364 func (dt *CommitDiffTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) { 365 dp := part.(diffPartition) 366 return dp.getRowIter(ctx, dt.ddb, dt.ss, dt.joiner) 367 }