Skip to content

Commit

Permalink
Implement sql schema
Browse files Browse the repository at this point in the history
  • Loading branch information
Bishop committed Apr 5, 2022
1 parent f23b869 commit e37413a
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 0 deletions.
254 changes: 254 additions & 0 deletions ability_cash/sql_schema/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package sql_schema

import (
"database/sql"
"github.com/Bishop/abilitycash2ledger/ledger"
_ "github.com/mattn/go-sqlite3"
"strings"
"time"

"github.com/Bishop/abilitycash2ledger/ability_cash/schema"
)

const AccountsSql = "SELECT Id, Name, StartingBalance, Currency FROM Accounts WHERE NOT Deleted"
const CurrenciesSql = "SELECT Id, Code, Precision FROM Currencies WHERE NOT Deleted"
const CategoriesSql = "SELECT Id, Name, Parent FROM Categories WHERE NOT Deleted ORDER BY Parent"
const RatesSql = "SELECT RateDate, Currency1, Currency2, Value1, Value2 FROM CurrencyRates WHERE NOT Deleted ORDER BY RateDate"
const TxCategoriesSql = "SELECT Category, \"Transaction\" FROM TransactionCategories WHERE NOT Deleted"
const TxsSql = "SELECT Id, BudgetDate, Locked, IncomeAccount, IncomeAmount, ExpenseAccount, ExpenseAmount, Comment FROM Transactions WHERE NOT Deleted AND Executed ORDER BY BudgetDate"

func ReadDatabase(fileName string) (schema.Database, error) {
db := NewDatabase()

base, err := sql.Open("sqlite3", fileName)
if err != nil {
return nil, err
}

err = readCurrencies(db, base)
if err != nil {
return nil, err
}

err = readRates(db, base)
if err != nil {
return nil, err
}

err = readAccounts(db, base)
if err != nil {
return nil, err
}

err = readCategories(db, base)
if err != nil {
return nil, err
}

err = readTxCategories(db, base)
if err != nil {
return nil, err
}

err = readTxs(db, base)
if err != nil {
return nil, err
}

return db, nil
}

func readCurrencies(db *Database, base *sql.DB) error {
return query(CurrenciesSql, db, base, func(uid int, rows *sql.Rows) error {
currency := Currency{}

err := rows.Scan(&uid, &currency.Code, &currency.Precision)
if err != nil {
return err
}

db.currenciesIndexI[uid] = &currency
db.currenciesIndexS[currency.Code] = &currency

return nil
})
}

func readRates(db *Database, base *sql.DB) error {
return query(RatesSql, db, base, func(uid int, rows *sql.Rows) error {
var currency1, currency2 int
var value1, value2 float64
var date int64

err := rows.Scan(&date, &currency1, &currency2, &value1, &value2)
if err != nil {
return err
}

db.Rates = append(db.Rates, schema.Rate{
Date: time.Unix(date, 0),
Currency1: db.currenciesIndexI[currency1].Code,
Currency2: db.currenciesIndexI[currency2].Code,
Amount1: db.currenciesIndexI[currency1].ConvertAmount(value1),
Amount2: db.currenciesIndexI[currency2].ConvertAmount(value2),
})

return nil
})
}

func readAccounts(db *Database, base *sql.DB) error {
return query(AccountsSql, db, base, func(uid int, rows *sql.Rows) error {
var currencyId int
account := schema.Account{}

err := rows.Scan(&uid, &account.Name, &account.InitBalance, &currencyId)
if err != nil {
return err
}

account.Currency = db.currenciesIndexI[currencyId].Code
account.InitBalance = db.currenciesIndexI[currencyId].ConvertAmount(account.InitBalance)

db.Accounts = append(db.Accounts, account)
db.accountIndex[uid] = &account

return nil
})
}

func readCategories(db *Database, base *sql.DB) error {
return query(CategoriesSql, db, base, func(uid int, rows *sql.Rows) error {
var parentId sql.NullInt32
var name string

err := rows.Scan(&uid, &name, &parentId)
if err != nil {
return err
}

if parentId.Valid {
name = db.categoriesIndex[int(parentId.Int32)] + "\\" + name
}

db.categoriesIndex[uid] = name

return nil
})
}

func readTxCategories(db *Database, base *sql.DB) error {
return query(TxCategoriesSql, db, base, func(uid int, rows *sql.Rows) error {
var category, tx int

err := rows.Scan(&category, &tx)
if err != nil {
return err
}

_, ok := db.txCategoriesIndex[tx]
if !ok {
db.txCategoriesIndex[tx] = make([]int, 0)
}
db.txCategoriesIndex[tx] = append(db.txCategoriesIndex[tx], category)

return nil
})
}

func readTxs(db *Database, base *sql.DB) error {
return query(TxsSql, db, base, func(uid int, rows *sql.Rows) error {
var iaccout, eaccount sql.NullInt32
var iamount, eamount sql.NullFloat64
var date int64
var locked bool
var comment string

err := rows.Scan(&uid, &date, &locked, &iaccout, &iamount, &eaccount, &eamount, &comment)
if err != nil {
return err
}

tx := ledger.Transaction{
Date: time.Unix(date, 0),
Cleared: locked,
Note: comment,
Metadata: make(map[string]string),
Items: []ledger.TxItem{},
}

if iaccout.Valid {
account := db.accountIndex[int(iaccout.Int32)]

item := ledger.TxItem{
Account: account.Name,
Currency: account.Currency,
Amount: db.currenciesIndexS[account.Currency].ConvertAmount(iamount.Float64),
}

tx.Items = append(tx.Items, item)
}

if eaccount.Valid {
account := db.accountIndex[int(eaccount.Int32)]

item := ledger.TxItem{
Account: account.Name,
Currency: account.Currency,
Amount: db.currenciesIndexS[account.Currency].ConvertAmount(eamount.Float64),
}

tx.Items = append(tx.Items, item)
}

if iaccout.Valid && eaccount.Valid {
if tx.Items[0].Currency == tx.Items[1].Currency {
tx.Payee = "Transfer"
} else {
tx.Payee = "Exchange"
}
}

if categories, ok := db.txCategoriesIndex[uid]; ok {
for _, category := range categories {
name := db.categoriesIndex[category]

nameParts := strings.SplitN(name, "\\", 2)

classifier := ""
switch nameParts[0] {
case "Income", "Expenses":
classifier = schema.ExpensesClassifier
case "Payee":
classifier = schema.PayeeClassifier
case "Agents":
classifier = "Agent"
}

tx.Metadata[classifier] = name
}
}

db.Transactions = append(db.Transactions, tx)

return nil
})
}

func query(query string, db *Database, base *sql.DB, callback func(uid int, rows *sql.Rows) error) error {
rows, err := base.Query(query)
if err != nil {
return err
}

var uid int

for rows.Next() {
err = callback(uid, rows)
if err != nil {
return err
}
}

return rows.Close()
}
66 changes: 66 additions & 0 deletions ability_cash/sql_schema/structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package sql_schema

import (
"github.com/Bishop/abilitycash2ledger/ability_cash/schema"
"github.com/Bishop/abilitycash2ledger/ledger"
"math"
)

type Database struct {
Rates []schema.Rate
Classifiers schema.ClassifiersList
Accounts []schema.Account
AccountsMap schema.AccountsMap
Transactions []ledger.Transaction
accountIndex map[int]*schema.Account
currenciesIndexI map[int]*Currency
currenciesIndexS map[string]*Currency
categoriesIndex map[int]string
txCategoriesIndex map[int][]int
}

type Currency struct {
Code string
Precision float64
}

func NewDatabase() *Database {
db := new(Database)

db.Rates = make([]schema.Rate, 0)
db.Classifiers = make(schema.ClassifiersList)
db.Classifiers[schema.ExpensesClassifier] = make([]string, 0)
db.Classifiers[schema.PayeeClassifier] = make([]string, 0)
db.Classifiers["Agent"] = make([]string, 0)
db.Accounts = make([]schema.Account, 0)
db.AccountsMap = make(schema.AccountsMap)
db.Transactions = make([]ledger.Transaction, 0)

db.accountIndex = make(map[int]*schema.Account)
db.currenciesIndexI = make(map[int]*Currency)
db.currenciesIndexS = make(map[string]*Currency)
db.categoriesIndex = make(map[int]string)
db.txCategoriesIndex = make(map[int][]int)

return db
}

func (d *Database) GetAccounts() *[]schema.Account {
return &d.Accounts
}

func (d *Database) GetTransactions() *[]ledger.Transaction {
return &d.Transactions
}

func (d *Database) GetClassifiers() *schema.ClassifiersList {
return &d.Classifiers
}

func (d *Database) GetRates() *[]schema.Rate {
return &d.Rates
}

func (c *Currency) ConvertAmount(amount float64) float64 {
return amount / math.Pow(10, c.Precision+2)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ require github.com/urfave/cli/v2 v2.4.0

require (
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.4.0 h1:m2pxjjDFgDxSPtO8WSdbndj17Wu2y8vOT86wE/tjr+I=
Expand Down
3 changes: 3 additions & 0 deletions scope/datafile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scope
import (
"errors"
"fmt"
"github.com/Bishop/abilitycash2ledger/ability_cash/sql_schema"
"os"
"path"
"text/template"
Expand All @@ -27,6 +28,8 @@ func (d *datafile) readDb() (schema.Database, error) {
return xml_schema.ReadDatabase(d.Path)
case "", ".csv":
return csv_schema.ReadDatabase(d.Path)
case ".cash":
return sql_schema.ReadDatabase(d.Path)
default:
return nil, errors.New("unknown format")
}
Expand Down

0 comments on commit e37413a

Please sign in to comment.