// Creates a new table. func (db *Database) Create(name string) (*table.Table, int) { var newTable *table.Table _, exists := db.Tables[name] if exists { return nil, st.TableAlreadyExists } if len(name) > constant.MaxTableNameLength { return nil, st.TableNameTooLong } // Create table files and directories. tablefilemanager.Create(db.Path, name) // Open the table var status int newTable, status = table.Open(db.Path, name) if status == st.OK { // Add default columns for columnName, length := range constant.DatabaseColumns() { status = newTable.Add(columnName, length) if status != st.OK { return nil, status } } db.Tables[name] = newTable } return newTable, st.OK }
// Load all rows of a table into RA result. func (r *Result) Load(t *table.Table) (*Result, int) { _, exists := r.Tables[t.Name] if exists { return r, st.TableAlreadyExists } // rowNumbers = list(range(t.NumberOfRows())) rowNumbers := make([]int, 0) numberOfRows, status := t.NumberOfRows() if status != st.OK { return r, status } for i := 0; i < numberOfRows; i++ { rowNumbers = append(rowNumbers[:], i) } r.Tables[t.Name] = &TableResult{t, rowNumbers} // Load columns of the table. for columnName, _ := range t.Columns { if !strings.HasPrefix(columnName, constant.ThePrefix) { _, exists := r.Aliases[columnName] if exists { logg.Warn("ra", "Load", "Column name "+columnName+" duplicates an existing alias") } r.Aliases[columnName] = &TableColumn{t.Name, columnName} } } return r, st.OK }
// Deletes rows in a table of RA result according to some select conditions. // The RA result is made a copy before using select conditions. func findAndDelete(t *table.Table, query *ra.Result, conditions ...ra.Condition) int { _, status := query.Copy().MultipleSelect(conditions...) if status != st.OK { return status } for _, i := range query.Tables[t.Name].RowNumbers { status = t.Delete(i) if status != st.OK { return status } } return t.Flush() }
func (tr *Transaction) Delete(t *table.Table, rowNumber int) int { // Execute "before delete" triggers. beforeTable, status := tr.DB.Get("~before") if status != st.OK { return status } row, status := t.Read(rowNumber) if status != st.OK { return status } triggerRA := ra.New() _, status = triggerRA.Load(beforeTable) if status != st.OK { return status } _, status = triggerRA.Select("TABLE", filter.Eq{}, t.Name) if status != st.OK { return status } status = trigger.ExecuteTrigger(tr.DB, t, triggerRA, "DE", row, nil) if status != st.OK { return status } // Update the row. status = t.Delete(rowNumber) if status != st.OK { return status } // Execute "after delete" triggers. afterTable, status := tr.DB.Get("~after") if status != st.OK { return status } triggerRA = ra.New() _, status = triggerRA.Load(afterTable) if status != st.OK { return status } _, status = triggerRA.Select("TABLE", filter.Eq{}, t.Name) if status != st.OK { return status } status = trigger.ExecuteTrigger(tr.DB, t, triggerRA, "DE", row, nil) if status != st.OK { return status } // Log the deleted row. tr.Log(&UndoDelete{t, rowNumber}) return st.OK }
func (tr *Transaction) Update(t *table.Table, rowNumber int, row map[string]string) int { // Execute "before update" triggers. beforeTable, status := tr.DB.Get("~before") if status != st.OK { return status } original, status := t.Read(rowNumber) if status != st.OK { return status } triggerRA := ra.New() _, status = triggerRA.Load(beforeTable) if status != st.OK { return status } _, status = triggerRA.Select("TABLE", filter.Eq{}, t.Name) if status != st.OK { return status } status = trigger.ExecuteTrigger(tr.DB, t, triggerRA, "UP", row, original) if status != st.OK { return status } // Update the row. status = t.Update(rowNumber, row) if status != st.OK { return status } // Execute "after update" triggers. afterTable, status := tr.DB.Get("~after") if status != st.OK { return status } triggerRA = ra.New() _, status = triggerRA.Load(afterTable) if status != st.OK { return status } _, status = triggerRA.Select("TABLE", filter.Eq{}, t.Name) if status != st.OK { return status } status = trigger.ExecuteTrigger(tr.DB, t, triggerRA, "UP", row, original) if status != st.OK { return status } // Log the updated row. tr.Log(&UndoUpdate{t, rowNumber, original}) return st.OK }
func (tr *Transaction) Insert(t *table.Table, row map[string]string) int { // Execute "before insert" triggers. beforeTable, status := tr.DB.Get("~before") if status != st.OK { return status } triggerRA := ra.New() _, status = triggerRA.Load(beforeTable) if status != st.OK { return status } _, status = triggerRA.Select("TABLE", filter.Eq{}, t.Name) if status != st.OK { return status } status = trigger.ExecuteTrigger(tr.DB, t, triggerRA, "IN", row, nil) if status != st.OK { return status } // Insert the new row to table. numberOfRows, status := t.NumberOfRows() if status != st.OK { return status } status = t.Insert(row) if status != st.OK { return status } // Execute "after insert" triggers. afterTable, status := tr.DB.Get("~after") if status != st.OK { return status } triggerRA = ra.New() _, status = triggerRA.Load(afterTable) if status != st.OK { return status } _, status = triggerRA.Select("TABLE", filter.Eq{}, t.Name) if status != st.OK { return status } status = trigger.ExecuteTrigger(tr.DB, t, triggerRA, "IN", row, nil) if status != st.OK { return status } // Log the inserted row. tr.Log(&UndoInsert{t, numberOfRows}) return st.OK }
// Look for a value in a table's column, returns true if the value is found. func find(column, value string, t *table.Table) (bool, int) { numberOfRows, status := t.NumberOfRows() if status != st.OK { return false, status } for i := 0; i < numberOfRows; i++ { row, status := t.Read(i) if status != st.OK { return false, status } if row[column] == value { return true, st.OK } } return false, st.OK }
// Relational algebra join using nested loops. func (r *Result) NLJoin(alias string, t2 *table.Table, name string) (*Result, int) { // t1 is the table in RA result. t1Column := r.Aliases[alias].ColumnName t1 := r.Tables[r.Aliases[alias].TableName] t2RowNumbers := make([]int, 0) // t2 is the external table. t2NumberOfRows, status := t2.NumberOfRows() if status != st.OK { return r, status } // Prepare to re-arrange the sequence of row numbers of all existing tables in RA result. newRowNumbers := make(map[string][]int) for name, _ := range r.Tables { newRowNumbers[name] = make([]int, 0) } // NL begins. for i, t1RowNumber := range t1.RowNumbers { for t2RowNumber := 0; t2RowNumber < t2NumberOfRows; t2RowNumber++ { t1Row, status := t1.Table.Read(t1RowNumber) if status != st.OK { return r, status } t2Row, status := t2.Read(t2RowNumber) if status != st.OK { return r, status } if t1Row["~del"] != "y" && t2Row["~del"] != "y" && t1Row[t1Column] == t2Row[name] { for name, _ := range newRowNumbers { newRowNumbers[name] = append(newRowNumbers[name][:], r.Tables[name].RowNumbers[i]) } t2RowNumbers = append(t2RowNumbers[:], t2RowNumber) } } } // Re-arrange the sequence of row numbers of all existing tables in RA result. for name, rowNumbers := range newRowNumbers { r.Tables[name].RowNumbers = rowNumbers } // Load columns of t2 into RA result. r.Load(t2) t2Table := r.Tables[t2.Name] t2Table.RowNumbers = t2RowNumbers return r, st.OK }
// Generate reports to output directory. func (report *Report) Generate(outputDir string) error { var buf bytes.Buffer var filename string switch report.period { case ReportPeriodInfinite: fmt.Fprintf(&buf, "All transactions\n\n") filename = filepath.Join(outputDir, "all.txt") case ReportPeriodYearly: fmt.Fprintf(&buf, "%d\n\n", report.from.Year()) filename = filepath.Join(outputDir, fmt.Sprintf("%d.txt", report.from.Year())) case ReportPeriodQuarterly: quarterName := fmt.Sprintf("Q%d", (report.from.Month()-1)/3+1) fmt.Fprintf(&buf, "%s %d\n\n", quarterName, report.from.Year()) filename = filepath.Join(outputDir, fmt.Sprintf("%d-%s.txt", report.from.Year(), quarterName)) case ReportPeriodMonthly: fmt.Fprintf(&buf, "%s %d\n\n", report.from.Month(), report.from.Year()) filename = filepath.Join(outputDir, fmt.Sprintf("%d-%02d.txt", report.from.Year(), report.from.Month())) default: return fmt.Errorf("unsupported report period %d", report.period) } file, err := os.Create(filename) if err != nil { return err } defer file.Close() var accounts []*Account for _, account := range report.accounts { accounts = append(accounts, account) } sort.Sort(sortByAccountName(accounts)) // Account summary t := new(table.Table) t.SetTitles(table.Row{ {Content: "account"}, {Content: "amount"}, {Content: "cumulative"}, {Content: "delta"}, }) for _, account := range accounts { cumulativeStr := balanceToString(account.CumulativeBalance()) deltaStr := fmt.Sprintf("%+.2f", report.AccountDelta(account.name)) if cumulativeStr != "-" || deltaStr != "+0.00" { t.AddRow(table.Row{ {Content: account.name.Leaf(), PadLeft: uint(indentAmount * account.name.Depth())}, {Content: balanceToString(account.FlatBalance()), Align: table.AlignRight}, {Content: cumulativeStr, Align: table.AlignRight}, {Content: deltaStr, Align: table.AlignRight}, }) } } buf.Write(t.RenderText()) // Transaction log fmt.Fprintf(&buf, "\nTransactions\n\n") t = new(table.Table) t.SetTitles(table.Row{ {Content: "date"}, {Content: "account"}, {Content: "debit"}, {Content: "credit"}, }) var prevDate time.Time for _, tr := range report.transactions { var dateStr string if tr.date != prevDate { dateStr = tr.date.Format(transactionDateFormat) } prevDate = tr.date t.AddRow(table.Row{ {Content: dateStr}, {Content: string(tr.accounts[Dr])}, {Content: fmt.Sprintf("%.2f", tr.amount), Align: table.AlignRight}, {Content: ""}, }) t.AddRow(table.Row{ {Content: ""}, {Content: string(tr.accounts[Cr])}, {Content: ""}, {Content: fmt.Sprintf("%.2f", tr.amount), Align: table.AlignRight}, }) } buf.Write(t.RenderText()) fmt.Fprintf(file, "%s", buf.Bytes()) return nil }