github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/model/store.go (about)

     1  package model
     2  
     3  import (
     4  	"context"
     5  	"encoding/base32"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"math"
    10  	"reflect"
    11  	"strings"
    12  	"unicode/utf8"
    13  
    14  	"github.com/tickoalcantara12/micro/v3/service/auth"
    15  	"github.com/tickoalcantara12/micro/v3/service/store"
    16  	"github.com/stoewer/go-strcase"
    17  )
    18  
    19  type model struct {
    20  	// the database used for querying
    21  	database string
    22  	// the table to use for the model
    23  	table string
    24  	// the primary index using id
    25  	idIndex Index
    26  	// helps logically separate keys in a model where
    27  	// multiple `Model`s share the same underlying
    28  	// physical database.
    29  	namespace string
    30  	// the user defined.options.Indexes maintained for queries
    31  	indexes []Index
    32  	// options accepted for the model
    33  	options *Options
    34  	// the instance of the model
    35  	instance interface{}
    36  }
    37  
    38  // NewModel returns a new model with options or uses internal defaults
    39  func NewModel(opts ...Option) Model {
    40  	var options Options
    41  
    42  	for _, o := range opts {
    43  		o(&options)
    44  	}
    45  
    46  	if options.Store == nil {
    47  		options.Store = store.DefaultStore
    48  	}
    49  
    50  	if len(options.Indexes) == 0 {
    51  		options.Indexes = append(options.Indexes, DefaultIndex)
    52  	}
    53  
    54  	return New(nil, &options)
    55  }
    56  
    57  // New returns a new model with the given values
    58  func New(instance interface{}, options *Options) Model {
    59  	if options == nil {
    60  		options = new(Options)
    61  	}
    62  
    63  	// indirect pointer types
    64  	// so we dont have to deal with pointers vs values down the line
    65  	if reflect.ValueOf(instance).Kind() == reflect.Ptr {
    66  		instance = reflect.Indirect(reflect.ValueOf(instance)).Interface()
    67  	}
    68  
    69  	var namespace, database, table string
    70  
    71  	// define namespace based on the value passed in
    72  	if instance != nil {
    73  		namespace = reflect.TypeOf(instance).String()
    74  	}
    75  
    76  	if len(options.Namespace) > 0 {
    77  		namespace = options.Namespace
    78  	}
    79  
    80  	if options.Store == nil {
    81  		options.Store = store.DefaultStore
    82  	}
    83  
    84  	if options.Context == nil {
    85  		options.Context = context.TODO()
    86  	}
    87  	if options.Key == "" {
    88  		var err error
    89  		options.Key, err = getKey(instance)
    90  		if err != nil {
    91  			// @todo throw panic? make new return error?
    92  			// CRUFT
    93  			options.Key = err.Error()
    94  		}
    95  	}
    96  	// the default index
    97  	idx := DefaultIndex
    98  
    99  	if len(options.Key) > 0 {
   100  		idx = newIndex(options.Key)
   101  	}
   102  
   103  	// set the database
   104  	database = options.Database
   105  	table = options.Table
   106  
   107  	// set defaults if blank
   108  	if len(database) == 0 && options.Store != nil {
   109  		database = options.Store.Options().Database
   110  	}
   111  
   112  	// set defaults if blank
   113  	if len(table) == 0 && options.Store != nil {
   114  		table = options.Store.Options().Table
   115  	}
   116  
   117  	return &model{
   118  		database:  database,
   119  		table:     table,
   120  		idIndex:   idx,
   121  		instance:  instance,
   122  		namespace: namespace,
   123  		options:   options,
   124  	}
   125  }
   126  
   127  func getKey(instance interface{}) (string, error) {
   128  	// will be registered later probably
   129  	if instance == nil {
   130  		return "", nil
   131  	}
   132  	idFields := []string{"ID", "Id", "id"}
   133  
   134  	switch v := instance.(type) {
   135  	case map[string]interface{}:
   136  		for _, idField := range idFields {
   137  			if _, ok := v[idField]; ok {
   138  				return idField, nil
   139  			}
   140  		}
   141  		// To support empty map schema
   142  		// db initializations, we return the default ID field
   143  		return "ID", nil
   144  	default:
   145  		val := reflect.ValueOf(instance)
   146  		for _, idField := range idFields {
   147  			if val.FieldByName(idField).IsValid() {
   148  				return idField, nil
   149  			}
   150  		}
   151  	}
   152  
   153  	return "", errors.New("ID Field not found")
   154  }
   155  
   156  // @todo we should correlate the field name with the model
   157  // instead of just blindly converting strings
   158  func (d *model) getFieldName(field string) string {
   159  	fieldName := ""
   160  	if strings.Contains(field, "_") {
   161  		fieldName = strcase.UpperCamelCase(field)
   162  	} else {
   163  		fieldName = strings.Title(field)
   164  	}
   165  	if fieldName == "ID" {
   166  		return d.options.Key
   167  	}
   168  	return fieldName
   169  }
   170  
   171  func (d *model) getFieldValue(struc interface{}, fieldName string) interface{} {
   172  	switch v := struc.(type) {
   173  	case map[string]interface{}:
   174  		return v[fieldName]
   175  	}
   176  
   177  	fieldName = d.getFieldName(fieldName)
   178  	r := reflect.ValueOf(struc)
   179  	f := reflect.Indirect(r).FieldByName(fieldName)
   180  
   181  	if !f.IsValid() {
   182  		return nil
   183  	}
   184  	return f.Interface()
   185  }
   186  
   187  func (d *model) setFieldValue(struc interface{}, fieldName string, value interface{}) {
   188  	switch v := struc.(type) {
   189  	case map[string]interface{}:
   190  		v[fieldName] = value
   191  		return
   192  	}
   193  
   194  	fieldName = d.getFieldName(fieldName)
   195  	r := reflect.ValueOf(struc)
   196  
   197  	f := reflect.Indirect(r).FieldByName(fieldName)
   198  	f.Set(reflect.ValueOf(value))
   199  }
   200  
   201  func (d *model) Context(ctx context.Context) Model {
   202  	// dereference the opts
   203  	opts := *d.options
   204  	opts.Context = ctx
   205  
   206  	// retrieve the account from context and override the database
   207  	acc, ok := auth.AccountFromContext(ctx)
   208  	if ok {
   209  		if len(acc.Issuer) > 0 {
   210  			// set the database to the account issuer
   211  			opts.Database = acc.Issuer
   212  		}
   213  	}
   214  
   215  	return &model{
   216  		database:  opts.Database,
   217  		table:     opts.Table,
   218  		idIndex:   d.idIndex,
   219  		instance:  d.instance,
   220  		namespace: d.namespace,
   221  		options:   &opts,
   222  	}
   223  }
   224  
   225  // Register an instance type of a model
   226  func (d *model) Register(instance interface{}) error {
   227  	if instance == nil {
   228  		return ErrorNilInterface
   229  	}
   230  	if reflect.ValueOf(instance).Kind() == reflect.Ptr {
   231  		instance = reflect.Indirect(reflect.ValueOf(instance)).Interface()
   232  	}
   233  	if d.options.Key == "" {
   234  		var err error
   235  		d.options.Key, err = getKey(instance)
   236  		if err != nil {
   237  			return err
   238  		}
   239  	}
   240  
   241  	// set the namespace
   242  	d.namespace = reflect.TypeOf(instance).String()
   243  	// TODO: add.options.Indexes?
   244  	d.instance = instance
   245  
   246  	return nil
   247  }
   248  
   249  func (d *model) Create(instance interface{}) error {
   250  	if reflect.ValueOf(instance).Kind() == reflect.Ptr {
   251  		instance = reflect.Indirect(reflect.ValueOf(instance)).Interface()
   252  	}
   253  	// @todo replace this hack with reflection
   254  	js, err := json.Marshal(instance)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	// get the old entries so we can compare values
   260  	// @todo consider some kind of locking (even if it's not distributed) by key here
   261  	// to avoid 2 read-writes happening at the same time
   262  	idQuery := d.idIndex.ToQuery(d.getFieldValue(instance, d.idIndex.FieldName))
   263  
   264  	var oldEntry interface{}
   265  	switch instance.(type) {
   266  	case map[string]interface{}:
   267  		oldEntry = map[string]interface{}{}
   268  	default:
   269  		oldEntry = reflect.New(reflect.ValueOf(instance).Type()).Interface()
   270  	}
   271  
   272  	err = d.Read(idQuery, &oldEntry)
   273  	if err != nil && err != ErrorNotFound {
   274  		return err
   275  	}
   276  
   277  	oldEntryFound := false
   278  	// map in interface can be non nil but empty
   279  	// so test for that
   280  	switch v := oldEntry.(type) {
   281  	case map[string]interface{}:
   282  		if len(v) > 0 {
   283  			oldEntryFound = true
   284  		}
   285  	default:
   286  		if oldEntry != nil {
   287  			oldEntryFound = true
   288  		}
   289  	}
   290  
   291  	// Do uniqueness checks before saving any data
   292  	for _, index := range d.options.Indexes {
   293  		if !index.Unique {
   294  			continue
   295  		}
   296  		potentialClash := reflect.New(reflect.ValueOf(instance).Type()).Interface()
   297  		err = d.Read(index.ToQuery(d.getFieldValue(instance, index.FieldName)), &potentialClash)
   298  		if err != nil && err != ErrorNotFound {
   299  			return err
   300  		}
   301  
   302  		if err == nil {
   303  			return errors.New("Unique index violation")
   304  		}
   305  	}
   306  
   307  	id := d.getFieldValue(instance, d.idIndex.FieldName)
   308  	for _, index := range append(d.options.Indexes, d.idIndex) {
   309  		// delete non id index keys to prevent stale index values
   310  		// ie.
   311  		//
   312  		//  # prefix  slug     id
   313  		//  postByTag/hi-there/1
   314  		//  # if slug gets changed to "hello-there" we will have two records
   315  		//  # without removing the old stale index:
   316  		//  postByTag/hi-there/1
   317  		//  postByTag/hello-there/1`
   318  		//
   319  		// @todo this check will only work for POD types, ie no slices or maps
   320  		// but it's not an issue as right now indexes are only supported on POD
   321  		// types anyway
   322  		if !indexesMatch(d.idIndex, index) &&
   323  			oldEntryFound &&
   324  			!reflect.DeepEqual(d.getFieldValue(oldEntry, index.FieldName), d.getFieldValue(instance, index.FieldName)) {
   325  
   326  			k := d.indexToKey(index, id, oldEntry, true)
   327  			// TODO: set the table name in the query
   328  			err = d.options.Store.Delete(k, store.DeleteFrom(d.database, d.table))
   329  			if err != nil {
   330  				return err
   331  			}
   332  		}
   333  		k := d.indexToKey(index, id, instance, true)
   334  		if d.options.Debug {
   335  			fmt.Printf("Saving key '%v', value: '%v'\n", k, string(js))
   336  		}
   337  		// TODO: set the table name in the query
   338  		err = d.options.Store.Write(&store.Record{
   339  			Key:   k,
   340  			Value: js,
   341  		}, store.WriteTo(d.database, d.table))
   342  		if err != nil {
   343  			return err
   344  		}
   345  	}
   346  	return nil
   347  }
   348  
   349  // TODO: implement the full functionality. Currently offloads to create.
   350  func (d *model) Update(v interface{}) error {
   351  	return d.Create(v)
   352  }
   353  
   354  func (d *model) Read(query Query, resultPointer interface{}) error {
   355  	t := reflect.TypeOf(resultPointer)
   356  
   357  	// check if it's a pointer
   358  	if v := t.Kind(); v != reflect.Ptr {
   359  		return fmt.Errorf("Require pointer type. Got %v", v)
   360  	}
   361  
   362  	// retrieve the non pointer type
   363  	t = t.Elem()
   364  
   365  	// if its a slice then use the list query method
   366  	if t.Kind() == reflect.Slice {
   367  		return d.list(query, resultPointer)
   368  	}
   369  
   370  	// otherwise continue on as normal
   371  	read := func(index Index) error {
   372  		k := d.queryToListKey(index, query)
   373  		if d.options.Debug {
   374  			fmt.Printf("Listing key '%v'\n", k)
   375  		}
   376  		// TODO: set the table name in the query
   377  		opts := []store.ReadOption{
   378  			store.ReadPrefix(),
   379  			store.ReadFrom(d.database, d.table),
   380  			store.ReadLimit(1),
   381  		}
   382  		recs, err := d.options.Store.Read(k, opts...)
   383  		if err != nil {
   384  			return err
   385  		}
   386  		if len(recs) == 0 {
   387  			return ErrorNotFound
   388  		}
   389  		if len(recs) > 1 {
   390  			return ErrorMultipleRecordsFound
   391  		}
   392  		if d.options.Debug {
   393  			fmt.Printf("Found value '%v'\n", string(recs[0].Value))
   394  		}
   395  		return json.Unmarshal(recs[0].Value, resultPointer)
   396  	}
   397  	if query.Type == queryTypeAll {
   398  		return read(Index{
   399  			Type:      indexTypeAll,
   400  			FieldName: d.options.Key,
   401  			Order:     d.idIndex.Order,
   402  		})
   403  	}
   404  	for _, index := range append(d.options.Indexes, d.idIndex) {
   405  		if indexMatchesQuery(index, query) {
   406  			return read(index)
   407  		}
   408  	}
   409  
   410  	// find a maching query if non exists, take the first one
   411  	// which applies to the same field regardless of ordering
   412  	// or padding etc.
   413  	for _, index := range append(d.options.Indexes, d.idIndex) {
   414  		if index.FieldName == query.FieldName {
   415  			return read(index)
   416  		}
   417  	}
   418  	return fmt.Errorf("Read: for query type '%v', field '%v' does not match any indexes", query.Type, query.FieldName)
   419  }
   420  
   421  func (d *model) list(query Query, resultSlicePointer interface{}) error {
   422  	list := func(index Index) error {
   423  		k := d.queryToListKey(index, query)
   424  		if d.options.Debug {
   425  			fmt.Printf("Listing key '%v'\n", k)
   426  		}
   427  
   428  		opts := []store.ReadOption{
   429  			store.ReadPrefix(),
   430  			store.ReadFrom(d.database, d.table),
   431  		}
   432  
   433  		if query.Limit > 0 {
   434  			opts = append(opts, store.ReadLimit(uint(query.Limit)))
   435  		}
   436  
   437  		if query.Offset > 0 {
   438  			opts = append(opts, store.ReadOffset(uint(query.Offset)))
   439  		}
   440  		recs, err := d.options.Store.Read(k, opts...)
   441  		if err != nil {
   442  			return err
   443  		}
   444  		// @todo speed this up with an actual buffer
   445  		jsBuffer := []byte("[")
   446  		for i, rec := range recs {
   447  			jsBuffer = append(jsBuffer, rec.Value...)
   448  			if i < len(recs)-1 {
   449  				jsBuffer = append(jsBuffer, []byte(",")...)
   450  			}
   451  		}
   452  		jsBuffer = append(jsBuffer, []byte("]")...)
   453  		if d.options.Debug {
   454  			fmt.Printf("Found values '%v'\n", string(jsBuffer))
   455  		}
   456  		return json.Unmarshal(jsBuffer, resultSlicePointer)
   457  	}
   458  
   459  	if query.Type == queryTypeAll {
   460  		return list(Index{
   461  			Type:      indexTypeAll,
   462  			FieldName: d.options.Key,
   463  			Order:     d.idIndex.Order,
   464  		})
   465  	}
   466  	for _, index := range append(d.options.Indexes, d.idIndex) {
   467  		if indexMatchesQuery(index, query) {
   468  			return list(index)
   469  		}
   470  	}
   471  
   472  	// find a maching query if non exists, take the first one
   473  	// which applies to the same field regardless of ordering
   474  	// or padding etc.
   475  	for _, index := range append(d.options.Indexes, d.idIndex) {
   476  		if index.FieldName == query.FieldName {
   477  			return list(index)
   478  		}
   479  	}
   480  
   481  	return fmt.Errorf("List: for query type '%v', field '%v' does not match any indexes", query.Type, query.FieldName)
   482  }
   483  
   484  func (d *model) queryToListKey(i Index, q Query) string {
   485  	if q.Value == nil {
   486  		return fmt.Sprintf("%v:%v", d.namespace, indexPrefix(i))
   487  	}
   488  	if i.FieldName != i.Order.FieldName && i.Order.FieldName != "" {
   489  		return fmt.Sprintf("%v:%v:%v", d.namespace, indexPrefix(i), q.Value)
   490  	}
   491  
   492  	var val interface{}
   493  	switch d.instance.(type) {
   494  	case map[string]interface{}:
   495  		val = map[string]interface{}{}
   496  	default:
   497  		val = reflect.New(reflect.ValueOf(d.instance).Type()).Interface()
   498  	}
   499  
   500  	if q.Value != nil {
   501  		d.setFieldValue(val, i.FieldName, q.Value)
   502  	}
   503  	return d.indexToKey(i, "", val, false)
   504  }
   505  
   506  // appendID true should be used when saving, false when querying
   507  // appendID false should also be used for 'id' indexes since they already have the unique
   508  // id. The reason id gets appended is make duplicated index keys unique.
   509  // ie.
   510  // # index # age # id
   511  // users/30/1
   512  // users/30/2
   513  // without ids we could only have one 30 year old user in the index
   514  func (d *model) indexToKey(i Index, id interface{}, entry interface{}, appendID bool) string {
   515  	if i.Type == indexTypeAll {
   516  		return fmt.Sprintf("%v:%v", d.namespace, indexPrefix(i))
   517  	}
   518  	if i.FieldName == "ID" {
   519  		i.FieldName = d.options.Key
   520  	}
   521  
   522  	format := "%v:%v"
   523  	values := []interface{}{d.namespace, indexPrefix(i)}
   524  	filterFieldValue := d.getFieldValue(entry, i.FieldName)
   525  	orderFieldValue := d.getFieldValue(entry, i.FieldName)
   526  	orderFieldKey := i.FieldName
   527  
   528  	if i.FieldName != i.Order.FieldName && i.Order.FieldName != "" {
   529  		orderFieldValue = d.getFieldValue(entry, i.Order.FieldName)
   530  		orderFieldKey = i.Order.FieldName
   531  	}
   532  
   533  	switch i.Type {
   534  	case indexTypeEq:
   535  		// If the filtering field is different than the ordering field,
   536  		// append the filter key to the key.
   537  		if i.FieldName != i.Order.FieldName && i.Order.FieldName != "" {
   538  			format += ":%v"
   539  			values = append(values, filterFieldValue)
   540  		}
   541  	}
   542  
   543  	// Handle the ordering part of the key.
   544  	// The filter and the ordering field might be the same
   545  	typ := reflect.TypeOf(orderFieldValue)
   546  	typName := "nil"
   547  	if typ != nil {
   548  		typName = typ.String()
   549  	}
   550  	format += ":%v"
   551  
   552  	switch v := orderFieldValue.(type) {
   553  	case string:
   554  		if i.Order.Type != OrderTypeUnordered {
   555  			values = append(values, d.getOrderedStringFieldKey(i, v))
   556  			break
   557  		}
   558  		values = append(values, v)
   559  	case int64:
   560  		// int64 gets padded to 19 characters as the maximum value of an int64
   561  		// is 9223372036854775807
   562  		// @todo handle negative numbers
   563  		if i.Order.Type == OrderTypeDesc {
   564  			values = append(values, fmt.Sprintf("%019d", math.MaxInt64-v))
   565  			break
   566  		}
   567  		values = append(values, fmt.Sprintf("%019d", v))
   568  	case float32:
   569  		// @todo fix display and padding of floats
   570  		if i.Order.Type == OrderTypeDesc {
   571  			values = append(values, fmt.Sprintf(i.FloatFormat, i.Float32Max-v))
   572  			break
   573  		}
   574  		values = append(values, fmt.Sprintf(i.FloatFormat, v))
   575  	case float64:
   576  		// @todo fix display and padding of floats
   577  		if i.Order.Type == OrderTypeDesc {
   578  			values = append(values, fmt.Sprintf(i.FloatFormat, i.Float64Max-v))
   579  			break
   580  		}
   581  		values = append(values, fmt.Sprintf(i.FloatFormat, v))
   582  	case int:
   583  		// int gets padded to the same length as int64 to gain
   584  		// resiliency in case of model type changes.
   585  		// This could be removed once migrations are implemented
   586  		// so savings in space for a type reflect in savings in space in the index too.
   587  		if i.Order.Type == OrderTypeDesc {
   588  			values = append(values, fmt.Sprintf("%019d", math.MaxInt32-v))
   589  			break
   590  		}
   591  		values = append(values, fmt.Sprintf("%019d", v))
   592  	case int32:
   593  		// int gets padded to the same length as int64 to gain
   594  		// resiliency in case of model type changes.
   595  		// This could be removed once migrations are implemented
   596  		// so savings in space for a type reflect in savings in space in the index too.
   597  		if i.Order.Type == OrderTypeDesc {
   598  			values = append(values, fmt.Sprintf("%019d", math.MaxInt32-v))
   599  			break
   600  		}
   601  		values = append(values, fmt.Sprintf("%019d", v))
   602  	case bool:
   603  		if i.Order.Type == OrderTypeDesc {
   604  			v = !v
   605  		}
   606  		values = append(values, v)
   607  	default:
   608  		panic("bug in code, unhandled type: " + typName + " for field '" + orderFieldKey + "' on type '" + reflect.TypeOf(d.instance).String() + "'")
   609  	}
   610  
   611  	if appendID {
   612  		format += ":%v"
   613  		values = append(values, id)
   614  	}
   615  	return fmt.Sprintf(format, values...)
   616  }
   617  
   618  // pad, reverse and optionally base32 encode string keys
   619  func (d *model) getOrderedStringFieldKey(i Index, fieldValue string) string {
   620  	runes := []rune{}
   621  	if i.Order.Type == OrderTypeDesc {
   622  		for _, char := range fieldValue {
   623  			runes = append(runes, utf8.MaxRune-char)
   624  		}
   625  	} else {
   626  		for _, char := range fieldValue {
   627  			runes = append(runes, char)
   628  		}
   629  	}
   630  
   631  	// padding the string to a fixed length
   632  	if len(runes) < i.StringOrderPadLength {
   633  		pad := []rune{}
   634  		for j := 0; j < i.StringOrderPadLength-len(runes); j++ {
   635  			if i.Order.Type == OrderTypeDesc {
   636  				pad = append(pad, utf8.MaxRune)
   637  			} else {
   638  				// space is the first non control operator char in ASCII
   639  				// consequently in Utf8 too so we use it as the minimal character here
   640  				// https://en.wikipedia.org/wiki/ASCII
   641  				//
   642  				// Displays somewhat unfortunately
   643  				// @todo think about a better min rune value to use here.
   644  				pad = append(pad, rune(32))
   645  			}
   646  		}
   647  		runes = append(runes, pad...)
   648  	}
   649  
   650  	var keyPart string
   651  	bs := []byte(string(runes))
   652  	if i.Order.Type == OrderTypeDesc {
   653  		if i.Base32Encode {
   654  			// base32 hex should be order preserving
   655  			// https://stackoverflow.com/questions/53301280/does-base64-encoding-preserve-alphabetical-ordering
   656  			dst := make([]byte, base32.HexEncoding.EncodedLen(len(bs)))
   657  			base32.HexEncoding.Encode(dst, bs)
   658  			// The `=` must be replaced with a lower value than the
   659  			// normal alphabet of the encoding since we want reverse order.
   660  			keyPart = strings.ReplaceAll(string(dst), "=", "0")
   661  		} else {
   662  			keyPart = string(bs)
   663  		}
   664  	} else {
   665  		keyPart = string(bs)
   666  
   667  	}
   668  	return keyPart
   669  }
   670  
   671  func (d *model) Delete(query Query) error {
   672  	oldEntry := reflect.New(reflect.ValueOf(d.instance).Type()).Interface()
   673  	switch oldEntry.(type) {
   674  	case *map[string]interface{}:
   675  		oldEntry = reflect.Indirect(reflect.ValueOf(oldEntry)).Interface()
   676  	}
   677  	err := d.Read(d.idIndex.ToQuery(query.Value), &oldEntry)
   678  	if err != nil {
   679  		return err
   680  	}
   681  
   682  	// first delete maintained.options.Indexes then id index
   683  	// if we delete id index first then the entry wont
   684  	// be deletable by id again but the maintained.options.Indexes
   685  	// will be stuck in limbo
   686  	for _, index := range append(d.options.Indexes, d.idIndex) {
   687  		key := d.indexToKey(index, d.getFieldValue(oldEntry, d.idIndex.FieldName), oldEntry, true)
   688  		if d.options.Debug {
   689  			fmt.Printf("Deleting key '%v'\n", key)
   690  		}
   691  		// TODO: set the table to delete from
   692  		err = d.options.Store.Delete(key, store.DeleteFrom(d.database, d.table))
   693  		if err != nil {
   694  			return err
   695  		}
   696  	}
   697  	return nil
   698  }