pgcache

package
v0.0.0-...-d31700d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2022 License: MIT Imports: 19 Imported by: 0

README

pgcache

Cache PostgreSQL table's data in memory and keep the cached data up to date automatically, by PostgreSQL's trigger and LISTEN/NOTIFY mechanisem.

Example

package pgcache_test

import (
	"database/sql"
	"fmt"
	"os"
	"runtime"
	"sync"
	"time"

	"gitee.com/go-better/dev/db/pg"
	loggerPkg "gitee.com/go-better/dev/debug/logger"
	"gitee.com/go-better/dev/db/pgcache"
)

var dbUrl = "postgres://postgres:@localhost/test?sslmode=disable"
var testDB = connectDB(dbUrl)
var logger = loggerPkg.New(os.Stderr)

type Student struct {
	Id    int64
	Name  string
	Class string
}

func Example() {
	initStudentsTable()

	var studentsMap = make(map[int64]Student)
	var classesMap = make(map[string][]Student)
	var mutex sync.RWMutex

	dbCache, err := pgcache.New(dbUrl, pg.New(testDB, time.Second), logger)
	if err != nil {
		panic(err)
	}
	_, err = dbCache.Add(&pgcache.Table{
		Name:      "students",
		RowStruct: Student{},
		Datas: []*pgcache.Data{
			{
				RWMutex: &mutex, DataPtr: &studentsMap, MapKeys: []string{"Id"},
			}, {
				RWMutex: &mutex, DataPtr: &classesMap, MapKeys: []string{"Class"},
				SortedSetUniqueKey: []string{"Id"},
			},
		},
	})
	if err != nil {
		panic(err)
	}

	// from now on, studentsMap and classesMap is always synchronized with students table.
	fmt.Println(`init:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)

	// even you insert some rows.
	testInsert(studentsMap, classesMap)
	// even you update some rows.
	testUpdate(studentsMap, classesMap)
	// even you delete some rows.
	testDelete(studentsMap, classesMap)

	dbCache.RemoveAll()

	// Output:
	// init:
	// map[1:{1 李雷 初三1班} 2:{2 韩梅梅 初三1班}]
	// map[初三1班:[{1 李雷 初三1班} {2 韩梅梅 初三1班}]]
	// after INSERT:
	// map[1:{1 李雷 初三1班} 2:{2 韩梅梅 初三1班} 3:{3 Lily 初三2班} 4:{4 Lucy 初三2班}]
	// map[初三1班:[{1 李雷 初三1班} {2 韩梅梅 初三1班}] 初三2班:[{3 Lily 初三2班} {4 Lucy 初三2班}]]
	// after UPDATE:
	// map[1:{1 李雷 初三2班} 2:{2 韩梅梅 初三2班} 3:{3 Lily 初三2班} 4:{4 Lucy 初三2班}]
	// map[初三2班:[{1 李雷 初三2班} {2 韩梅梅 初三2班} {3 Lily 初三2班} {4 Lucy 初三2班}]]
	// after DELETE:
	// map[1:{1 李雷 初三2班} 2:{2 韩梅梅 初三2班}]
	// map[初三2班:[{1 李雷 初三2班} {2 韩梅梅 初三2班}]]
}

func initStudentsTable() {
	if _, err := testDB.Exec(`
DROP TABLE IF EXISTS students;
CREATE TABLE IF NOT EXISTS students (
  id    bigserial,
  name  text,
  class text
);
INSERT INTO students (id, name, class)
VALUES
(1, '李雷',   '初三1班'),
(2, '韩梅梅', '初三1班');
`); err != nil {
		panic(err)
	}
}

func testInsert(studentsMap map[int64]Student, classesMap map[string][]Student) {
	if _, err := testDB.Exec(`
INSERT INTO students (id, name, class)
VALUES
(3, 'Lily',   '初三2班'),
(4, 'Lucy',   '初三2班');
`); err != nil {
		panic(err)
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println(`after INSERT:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)
}

func testUpdate(studentsMap map[int64]Student, classesMap map[string][]Student) {
	if _, err := testDB.Exec(`UPDATE students SET "class" = '初三2班'`); err != nil {
		panic(err)
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println(`after UPDATE:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)
}

func testDelete(studentsMap map[int64]Student, classesMap map[string][]Student) {
	if _, err := testDB.Exec(`DELETE FROM students WHERE id in (3, 4)`); err != nil {
		panic(err)
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println(`after DELETE:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)
}

func connectDB(dbUrl string) *sql.DB {
	db, err := sql.Open(`postgres`, dbUrl)
	if err != nil {
		panic(err)
	}
	return db
}

Documentation

Overview

Example
package main

import (
	"database/sql"
	"fmt"
	"os"
	"sync"
	"time"

	"gitee.com/go-better/dev/db/pg"
	"gitee.com/go-better/dev/db/pgcache"
	loggerPkg "gitee.com/go-better/dev/debug/logger"
)

var dbUrl = "postgres://postgres:postgres@localhost/postgres?sslmode=disable"
var testDB = connectDB(dbUrl)
var logger = loggerPkg.New(os.Stderr)

type Student struct {
	Id        int64
	Name      string
	Class     string
	UpdatedAt time.Time
}

func (s Student) String() string {
	return fmt.Sprintf(`{%d %s %s %s}`, s.Id, s.Name, s.Class,
		s.UpdatedAt.Format(`2006-01-02 15:04:05 Z0700`))
}

func main() {
	initStudentsTable()

	var studentsMap = make(map[int64]Student)
	var classesMap = make(map[string][]Student)
	var mutex sync.RWMutex

	dbCache, err := pgcache.New(dbUrl, pg.New(testDB, time.Second), logger)
	if err != nil {
		panic(err)
	}
	_, err = dbCache.Add(&pgcache.Table{
		Name:      "students",
		RowStruct: Student{},
		Datas: []*pgcache.Data{
			{
				RWMutex: &mutex, DataPtr: &studentsMap, MapKeys: []string{"Id"},
			}, {
				RWMutex: &mutex, DataPtr: &classesMap, MapKeys: []string{"Class"},
				SortedSetUniqueKey: []string{"Id"},
			},
		},
	})
	if err != nil {
		panic(err)
	}

	// from now on, studentsMap and classesMap is always synchronized with students table.
	fmt.Println(`init:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)

	// even you insert some rows.
	testInsert(studentsMap, classesMap)
	// even you update some rows.
	testUpdate(studentsMap, classesMap)
	// even you delete some rows.
	testDelete(studentsMap, classesMap)

	dbCache.RemoveAll()

}

func initStudentsTable() {
	if _, err := testDB.Exec(`
SET TIME ZONE 'PRC';
DROP TABLE IF EXISTS students;
CREATE TABLE IF NOT EXISTS students (
  id    bigserial,
  name  text,
  class text,
  updated_at timestamptz
);
INSERT INTO students (id, name, class, updated_at)
VALUES
(1, '李雷',   '初三1班', '2003-10-1T09:10:10+08:00'),
(2, '韩梅梅', '初三1班', '2003-10-1T09:10:20+08:00');
`); err != nil {
		panic(err)
	}
}

func testInsert(studentsMap map[int64]Student, classesMap map[string][]Student) {
	if _, err := testDB.Exec(`
INSERT INTO students (id, name, class, updated_at)
VALUES
(3, 'Lily',   '初三2班', '2003-10-1T09:10:30+08:00'),
(4, 'Lucy',   '初三2班', '2003-10-1T09:10:30+08:00');
`); err != nil {
		panic(err)
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println(`after INSERT:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)
}

func testUpdate(studentsMap map[int64]Student, classesMap map[string][]Student) {
	if _, err := testDB.Exec(
		`UPDATE students SET "class" = '初三2班', updated_at = '2003-10-1 09:10:40+08:00'`,
	); err != nil {
		panic(err)
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println(`after UPDATE:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)
}

func testDelete(studentsMap map[int64]Student, classesMap map[string][]Student) {
	if _, err := testDB.Exec(`DELETE FROM students WHERE id in (3, 4)`); err != nil {
		panic(err)
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println(`after DELETE:`)
	fmt.Println(studentsMap)
	fmt.Println(classesMap)
}

func connectDB(dbUrl string) *sql.DB {
	db, err := sql.Open(`postgres`, dbUrl)
	if err != nil {
		panic(err)
	}
	return db
}
Output:

init:
map[1:{1 李雷 初三1班 2003-10-01 09:10:10 +0800} 2:{2 韩梅梅 初三1班 2003-10-01 09:10:20 +0800}]
map[初三1班:[{1 李雷 初三1班 2003-10-01 09:10:10 +0800} {2 韩梅梅 初三1班 2003-10-01 09:10:20 +0800}]]
after INSERT:
map[1:{1 李雷 初三1班 2003-10-01 09:10:10 +0800} 2:{2 韩梅梅 初三1班 2003-10-01 09:10:20 +0800} 3:{3 Lily 初三2班 2003-10-01 09:10:30 +0800} 4:{4 Lucy 初三2班 2003-10-01 09:10:30 +0800}]
map[初三1班:[{1 李雷 初三1班 2003-10-01 09:10:10 +0800} {2 韩梅梅 初三1班 2003-10-01 09:10:20 +0800}] 初三2班:[{3 Lily 初三2班 2003-10-01 09:10:30 +0800} {4 Lucy 初三2班 2003-10-01 09:10:30 +0800}]]
after UPDATE:
map[1:{1 李雷 初三2班 2003-10-01 09:10:40 +0800} 2:{2 韩梅梅 初三2班 2003-10-01 09:10:40 +0800} 3:{3 Lily 初三2班 2003-10-01 09:10:40 +0800} 4:{4 Lucy 初三2班 2003-10-01 09:10:40 +0800}]
map[初三2班:[{1 李雷 初三2班 2003-10-01 09:10:40 +0800} {2 韩梅梅 初三2班 2003-10-01 09:10:40 +0800} {3 Lily 初三2班 2003-10-01 09:10:40 +0800} {4 Lucy 初三2班 2003-10-01 09:10:40 +0800}]]
after DELETE:
map[1:{1 李雷 初三2班 2003-10-01 09:10:40 +0800} 2:{2 韩梅梅 初三2班 2003-10-01 09:10:40 +0800}]
map[初三2班:[{1 李雷 初三2班 2003-10-01 09:10:40 +0800} {2 韩梅梅 初三2班 2003-10-01 09:10:40 +0800}]]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Field2Column

func Field2Column(str string) string
单词边界有两种

1. 非大写字符,且下一个是大写字符 2. 大写字符,且下一个是大写字符,且下下一个是非大写字符

Types

type DB

type DB struct {
	// contains filtered or unexported fields
}

func New

func New(dbAddr string, dbQuerier DBQuerier, logger Logger) (*DB, error)

func (*DB) Add

func (db *DB) Add(table *Table) (*Table, error)

func (*DB) Remove

func (db *DB) Remove(table string) error

func (*DB) RemoveAll

func (db *DB) RemoveAll() error

type DBQuerier

type DBQuerier interface {
	Query(data interface{}, sql string, args ...interface{}) error
	GetDB() *sql.DB
}

type Data

type Data struct {
	*sync.RWMutex
	// DataPtr is a pointer to a map or slice to store data, required.
	DataPtr interface{}
	// MapKeys is the field names to get map keys from row struct, required if DataPtr is a map.
	MapKeys []string
	// Value is the field name to get map or slice value from row struct.
	// If it's empty, the whole row struct is used.
	Value string

	// If the DataPtr or map value is a slice, it's used as sorted set. If it's a sorted set of struct,
	// SortedSetUniqueKey is required, it specifies the fields used as unique key.
	SortedSetUniqueKey []string

	// Preprocess is optional. It's a method name of row struct. It should be of "func ()" form.
	// It is called before Precond method is called.
	Preprocess string
	// Precond is optional. It's a method name of row struct. It should be of "func () bool" form.
	// It is called before handling, if the return value is false, no handling(save or remove) is performed.
	Precond string
	// contains filtered or unexported fields
}
Example (Init_flags1)
mutex := sync.RWMutex{}
var m map[int]map[string][]*int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId", "Subject"}, Value: "Score",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
fmt.Println(d.isSortedSets, d.realValueIsPointer)
Output:

<nil>
true true
Example (Init_flags2)
mutex := sync.RWMutex{}
var m map[int][]*Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	SortedSetUniqueKey: []string{"Subject"},
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
fmt.Println(d.isSortedSets, d.realValueIsPointer)
Output:

<nil>
true true
Example (Init_invalidDataPtr_1)
mutex := sync.RWMutex{}

d := Data{RWMutex: &mutex, DataPtr: map[int]int{}}
fmt.Println(d.init(nil))
Output:

Data.DataPtr should be a non nil pointer to a map or slice.
Example (Init_invalidDataPtr_2)
mutex := sync.RWMutex{}
var p *map[int]int
d := Data{RWMutex: &mutex, DataPtr: p}
fmt.Println(d.init(nil))
Output:

Data.DataPtr should be a non nil pointer to a map or slice.
Example (Init_invalidMapKeys_1)
mutex := sync.RWMutex{}
var m map[int]int
d := Data{RWMutex: &mutex, DataPtr: &m}
fmt.Println(d.init(nil))
Output:

Data.DataPtr is a 1 layers map, but Data.MapKeys has 0 field.
Example (Init_invalidMapKeys_2)
mutex := sync.RWMutex{}
var m map[int]map[string]int
d := Data{RWMutex: &mutex, DataPtr: &m, MapKeys: []string{"StudentId", "Subject", "Other"}}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.DataPtr is a 2 layers map, but Data.MapKeys has 3 field.
Example (Init_invalidMapKeys_3)
mutex := sync.RWMutex{}
var m map[int]map[string]int
d := Data{RWMutex: &mutex, DataPtr: &m, MapKeys: []string{"Student", "Subject"}}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.MapKeys[0]: Student, no such field in row struct.
Example (Init_invalidMapKeys_4)
mutex := sync.RWMutex{}
var m map[int]map[string]int
d := Data{RWMutex: &mutex, DataPtr: &m, MapKeys: []string{"Subject", "StudentId"}}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.MapKeys[0]: Subject, type string is not assignable to int.
Example (Init_invalidMapKeys_5)
mutex := sync.RWMutex{}
var s []int
d := Data{RWMutex: &mutex, DataPtr: &s, MapKeys: []string{"Subject", "StudentId"}}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.DataPtr is a slice, so Data.MapKeys should be empty.
Example (Init_invalidPrecond_1)
mutex := sync.RWMutex{}
var m map[int]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	Precond: "None",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.Precond: None, no such method for the row struct.
Example (Init_invalidPrecond_2)
mutex := sync.RWMutex{}
var m map[int]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	Precond: "Other",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.Precond: Other, should be of "func () bool" form.
Example (Init_invalidSortedSetUniqueKey_1)
mutex := sync.RWMutex{}
var m map[int]map[string]int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId", "Subject"}, Value: "Score",
	SortedSetUniqueKey: []string{"Other"},
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.SortedSetUniqueKey should be empty.
Example (Init_invalidSortedSetUniqueKey_2)
mutex := sync.RWMutex{}
var m map[int]map[string][]int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId", "Subject"}, Value: "Score",
	SortedSetUniqueKey: []string{"Other"},
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.SortedSetUniqueKey should be empty.
Example (Init_invalidSortedSetUniqueKey_3)
mutex := sync.RWMutex{}
var m map[int][]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	SortedSetUniqueKey: []string{},
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.SortedSetUniqueKey should not be empty.
Example (Init_invalidSortedSetUniqueKey_4)
mutex := sync.RWMutex{}
var m map[int][]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	SortedSetUniqueKey: []string{"Other"},
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.SortedSetUniqueKey[0]: Other, no such field in value struct.
Example (Init_invalidSortedSetUniqueKey_5)
mutex := sync.RWMutex{}
type score2 struct {
	Score
	ScoreFloat float32
}
var m map[int][]score2
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	SortedSetUniqueKey: []string{"ScoreFloat"},
}
fmt.Println(d.init(reflect.TypeOf(score2{})))
Output:

Data.SortedSetUniqueKey[0]: ScoreFloat, should be a integer or string type.
Example (Init_invalidValue_1)
mutex := sync.RWMutex{}
var m map[int]map[string]int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId", "Subject"}, Value: "theScore",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.Value: theScore, no such field in row struct.
Example (Init_invalidValue_2)
mutex := sync.RWMutex{}
var m map[int]map[string]float32
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId", "Subject"}, Value: "Score",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.Value: Score, type int is not assignable to float32.
Example (Init_invalidValue_3)
mutex := sync.RWMutex{}
var s []*Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &s, Value: "Score",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
Output:

Data.Value: Score, type int is not assignable to *pgcache.Score.
Example (Init_nilMutex)
d := Data{}
fmt.Println(d.init(nil))
Output:

Data.RWMutex is nil.
Example (Init_validPrecond_1)
mutex := sync.RWMutex{}
var m map[int]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	Precond: "Valid",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
fmt.Println(d.precondMethodIndex)
Output:

<nil>
1
Example (Init_validPrecond_2)
mutex := sync.RWMutex{}
var m map[int]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	Precond: "Valid2",
}
fmt.Println(d.init(reflect.TypeOf(Score{})))
fmt.Println(d.precondMethodIndex)
Output:

<nil>
2
Example (Precond_1)
mutex := sync.RWMutex{}
var m map[int]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	Precond: "Valid",
}
d.init(reflect.TypeOf(Score{}))
fmt.Println(
	d.precond(reflect.ValueOf(&Score{Score: 1}).Elem()),
	d.precond(reflect.ValueOf(&Score{Score: -1}).Elem()),
)
Output:

true false
Example (Precond_2)
mutex := sync.RWMutex{}
var m map[int]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"},
	Precond: "Valid2",
}
d.init(reflect.TypeOf(Score{}))
fmt.Println(
	d.precond(reflect.ValueOf(&Score{Score: 1}).Elem()),
	d.precond(reflect.ValueOf(&Score{Score: -1}).Elem()),
)
Output:

true false
Example (Save_remove_clear)
mutex := sync.RWMutex{}
var m map[int]int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"}, Value: "Score",
	Precond: "Valid",
}
d.init(reflect.TypeOf(Score{}))
d.clear()

rows := reflect.ValueOf([]Score{
	{StudentId: 1001, Score: 98},
	{StudentId: 1002, Score: 101},
	{StudentId: 1003, Score: 99},
	{StudentId: 1002, Score: 100},
})
for i := 0; i < rows.Len(); i++ {
	d.save(rows.Index(i))
}
fmt.Println(m)

rows = reflect.ValueOf([]Score{
	{StudentId: 1002, Score: 100},
	{StudentId: 1004},
})
for i := 0; i < rows.Len(); i++ {
	d.remove(rows.Index(i))
}
fmt.Println(m)

d.clear()
fmt.Println(m)
Output:

map[1001:98 1002:100 1003:99]
map[1001:98 1003:99]
map[]
Example (Save_remove_clear_sortedset)
mutex := sync.RWMutex{}
var m map[int][]int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"}, Value: "Score",
	Precond: "Valid",
}
d.init(reflect.TypeOf(Score{}))
rows := reflect.ValueOf([]Score{
	{StudentId: 1001, Score: 98},
	{StudentId: 1001, Score: 99},
	{StudentId: 1002, Score: 90},
	{StudentId: 1003, Score: 99},
	{StudentId: 1002, Score: 91},
	{StudentId: 1003, Score: 100},
	{StudentId: 1001, Score: 99},
})
for i := 0; i < rows.Len(); i++ {
	d.save(rows.Index(i))
}
fmt.Println(m)

rows = reflect.ValueOf([]Score{
	{StudentId: 1002, Score: 90},
	{StudentId: 1002, Score: 91},
	{StudentId: 1003, Score: 100},
	{StudentId: 1004},
})
for i := 0; i < rows.Len(); i++ {
	d.remove(rows.Index(i))
}
fmt.Println(m)

d.clear()
fmt.Println(m)
Output:

map[1001:[98 99] 1002:[90 91] 1003:[99 100]]
map[1001:[98 99] 1003:[99]]
map[]
Example (Save_remove_clear_sortedset_2)
mutex := sync.RWMutex{}
var m map[int][]Score
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId"}, SortedSetUniqueKey: []string{"Subject"},
}
d.init(reflect.TypeOf(Score{}))
rows := reflect.ValueOf([]Score{
	{StudentId: 1001, Subject: "语文", Score: 98},
	{StudentId: 1001, Subject: "语文", Score: 99},
	{StudentId: 1002, Subject: "数学", Score: 90},
	{StudentId: 1003, Subject: "语文", Score: 99},
	{StudentId: 1002, Subject: "数学", Score: 91},
	{StudentId: 1003, Subject: "数学", Score: 100},
})
for i := 0; i < rows.Len(); i++ {
	d.save(rows.Index(i))
}
fmt.Println(m)

rows = reflect.ValueOf([]Score{
	{StudentId: 1001, Subject: "语文", Score: 90},
	{StudentId: 1002, Subject: "数学", Score: 90},
	{StudentId: 1003, Subject: "数学"},
	{StudentId: 1004, Subject: "语文"},
})
for i := 0; i < rows.Len(); i++ {
	d.remove(rows.Index(i))
}
fmt.Println(m)

d.clear()
fmt.Println(m)
Output:

map[1001:[{1001 语文 99}] 1002:[{1002 数学 91}] 1003:[{1003 数学 100} {1003 语文 99}]]
map[1003:[{1003 语文 99}]]
map[]
Example (Save_remove_clear_sortedset_3)
mutex := sync.RWMutex{}
var m map[int]map[string][]int
d := Data{
	RWMutex: &mutex,
	DataPtr: &m, MapKeys: []string{"StudentId", "Subject"}, Value: "Score",
}
d.init(reflect.TypeOf(Score{}))
rows := reflect.ValueOf([]Score{
	{StudentId: 1001, Subject: "语文", Score: 98},
	{StudentId: 1001, Subject: "语文", Score: 99},
	{StudentId: 1002, Subject: "数学", Score: 91},
	{StudentId: 1003, Subject: "语文", Score: 99},
	{StudentId: 1002, Subject: "数学", Score: 90},
	{StudentId: 1003, Subject: "数学", Score: 100},
})
for i := 0; i < rows.Len(); i++ {
	d.save(rows.Index(i))
}
fmt.Println(m)

rows = reflect.ValueOf([]Score{
	{StudentId: 1002, Subject: "数学", Score: 90},
	{StudentId: 1002, Subject: "数学", Score: 91},
	{StudentId: 1004, Subject: "语文"},
})
for i := 0; i < rows.Len(); i++ {
	d.remove(rows.Index(i))
}
fmt.Println(m)

m = make(map[int]map[string][]int)
fmt.Println(d.dataV)
Output:

map[1001:map[语文:[98 99]] 1002:map[数学:[90 91]] 1003:map[数学:[100] 语文:[99]]]
map[1001:map[语文:[98 99]] 1002:map[] 1003:map[数学:[100] 语文:[99]]]
map[]

func (*Data) Data

func (d *Data) Data(keys ...string) (interface{}, error)

func (*Data) Key

func (d *Data) Key() string

func (*Data) Size

func (d *Data) Size() int

type Logger

type Logger interface {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
}

type Table

type Table struct {

	// The name of the table to cache, required.
	Name string

	// The struct to receive a table row.
	RowStruct interface{}

	// The columns of the table to cache. It's got from the pg_notify payload, it must be less than
	// 8000 bytes, use "BigColumns" if necessarry.
	// If empty, the fields of "RowStruct" which is not "BigColumns" are used.
	// The field name is converted to underscore style, and field with `json:"-"` tag is ignored.
	Columns string

	// The big columns of the table to cache. It's got by a seperate query.
	// Warning: when update, it will not be set on the old value.
	BigColumns string
	// The unique fields to load "BigColumns" from db. If empty, and "RowStruct" has a "Id" Field,
	// it's used as "BigColumnsLoadKeys".
	BigColumnsLoadKeys []string

	// The sql used to load initial data when a table is cached, or reload table data when the db
	// connection lost. If empty, "Columns" and "BigColumns" is used to make a SELECT sql FROM "NAME".
	LoadSql string

	// Datas is the maps to store table rows.
	Datas []*Data
	// contains filtered or unexported fields
}

A Handler to cache table data.

Example
var m1 map[int]map[string]int
var m2 map[string]map[int]int

var mutex sync.RWMutex
t := &Table{
	Name:      "scores",
	RowStruct: Score{},
	Datas: []*Data{
		{RWMutex: &mutex, DataPtr: &m1, MapKeys: []string{"StudentId", "Subject"}, Value: "Score"},
		{RWMutex: &mutex, DataPtr: &m2, MapKeys: []string{"Subject", "StudentId"}, Value: "Score"},
	},
}
t.init("db", testQuerier{}, testLogger)

t.Init("")
fmt.Println(m1, m2)

t.Create("", []byte(`{"StudentId": 1001, "Subject": "语文", "Score": 95}`))
fmt.Println(m1, m2)

t.Update("",
	[]byte(`{"StudentId": 1001, "Subject": "语文", "Score": 95}`),
	[]byte(`{"StudentId": 1001, "Subject": "数学", "Score": 96}`),
)
fmt.Println(m1, m2)

t.Delete("",
	[]byte(`{"StudentId": 1001, "Subject": "数学", "Score": 96}`),
)
fmt.Println(m1, m2)

t.Clear()
fmt.Println(m1, m2)

t.Create("", []byte(`{"StudentId": 1001, "Subject": "语文", "Score": 95}`))
fmt.Println(m1, m2)
Output:

map[1000:map[语文:90]] map[语文:map[1000:90]]
map[1000:map[语文:90] 1001:map[语文:95]] map[语文:map[1000:90 1001:95]]
map[1000:map[语文:90] 1001:map[数学:96]] map[数学:map[1001:96] 语文:map[1000:90]]
map[1000:map[语文:90] 1001:map[]] map[数学:map[] 语文:map[1000:90]]
map[] map[]
map[1001:map[语文:95]] map[语文:map[1001:95]]
Example (Init)
t := Table{
	Name: "scores",
	RowStruct: struct {
		Score
		Z bool `json:"-"`
	}{},
}
t.init("", testQuerier{}, testLogger)
fmt.Println(t.Columns)
fmt.Println(t.BigColumns)
fmt.Println(t.LoadSql)
Output:

student_id,subject,score

SELECT student_id,subject,score  FROM scores
Example (Init_bigColumns)
t := Table{
	Name:               "scores",
	RowStruct:          Score{},
	BigColumns:         "score",
	BigColumnsLoadKeys: []string{"StudentId", "Subject"},
}
t.init("", testQuerier{}, testLogger)
fmt.Println(t.Columns)
fmt.Println(t.BigColumns)
fmt.Println(t.bigColumnsLoadSql)
fmt.Println(t.LoadSql)
Output:

student_id,subject
score
SELECT score FROM scores WHERE student_id = %s AND subject = %s
SELECT student_id,subject ,score FROM scores

func (*Table) Clear

func (t *Table) Clear()

func (*Table) ConnLoss

func (t *Table) ConnLoss(table string)

func (*Table) Create

func (t *Table) Create(table string, content []byte)

func (*Table) Delete

func (t *Table) Delete(table string, content []byte)

func (*Table) Error

func (t *Table) Error(err interface{})

func (*Table) GetDatas

func (t *Table) GetDatas() []manage.Data

func (*Table) Init

func (t *Table) Init(table string)

func (*Table) Reload

func (t *Table) Reload() error

func (*Table) Remove

func (t *Table) Remove(rows interface{})

func (*Table) Save

func (t *Table) Save(rows interface{})

func (*Table) Update

func (t *Table) Update(table string, oldContent, newContent []byte)

Directories

Path Synopsis
vim: set ft=html:
vim: set ft=html:
适用于低频修改且量小的数据,单线程操作。
适用于低频修改且量小的数据,单线程操作。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL