github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/ext/pivot/pivot_test.go (about)

     1  package pivot_test
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/ncruces/go-sqlite3"
    10  	_ "github.com/ncruces/go-sqlite3/embed"
    11  	"github.com/ncruces/go-sqlite3/ext/pivot"
    12  	_ "github.com/ncruces/go-sqlite3/tests/testcfg"
    13  )
    14  
    15  // https://antonz.org/sqlite-pivot-table/
    16  func Example() {
    17  	db, err := sqlite3.Open(":memory:")
    18  	if err != nil {
    19  		log.Fatal(err)
    20  	}
    21  	defer db.Close()
    22  
    23  	pivot.Register(db)
    24  
    25  	err = db.Exec(`
    26  		CREATE TABLE sales(product TEXT, year INT, income DECIMAL);
    27  		INSERT INTO  sales(product, year, income) VALUES
    28  			('alpha', 2020, 100),
    29  			('alpha', 2021, 120),
    30  			('alpha', 2022, 130),
    31  			('alpha', 2023, 140),
    32  			('beta',  2020, 10),
    33  			('beta',  2021, 20),
    34  			('beta',  2022, 40),
    35  			('beta',  2023, 80),
    36  			('gamma', 2020, 80),
    37  			('gamma', 2021, 75),
    38  			('gamma', 2022, 78),
    39  			('gamma', 2023, 80);
    40  	`)
    41  	if err != nil {
    42  		log.Fatal(err)
    43  	}
    44  
    45  	err = db.Exec(`
    46  		CREATE VIRTUAL TABLE v_sales USING pivot(
    47  			-- rows
    48  			(SELECT DISTINCT product FROM sales),
    49  			-- columns
    50  			(SELECT DISTINCT year, year FROM sales),
    51  			-- cells
    52  			(SELECT sum(income) FROM sales WHERE product = ? AND year = ?)
    53  		)`)
    54  	if err != nil {
    55  		log.Fatal(err)
    56  	}
    57  
    58  	stmt, _, err := db.Prepare(`SELECT * FROM v_sales`)
    59  	if err != nil {
    60  		log.Fatal(err)
    61  	}
    62  	defer stmt.Close()
    63  
    64  	cols := make([]string, stmt.ColumnCount())
    65  	for i := range cols {
    66  		cols[i] = stmt.ColumnName(i)
    67  	}
    68  	fmt.Println(pretty(cols))
    69  	for stmt.Step() {
    70  		for i := range cols {
    71  			cols[i] = stmt.ColumnText(i)
    72  		}
    73  		fmt.Println(pretty(cols))
    74  	}
    75  	if err := stmt.Reset(); err != nil {
    76  		log.Fatal(err)
    77  	}
    78  
    79  	// Output:
    80  	// product 2020    2021    2022    2023
    81  	// alpha   100     120     130     140
    82  	// beta    10      20      40      80
    83  	// gamma   80      75      78      80
    84  }
    85  
    86  func TestRegister(t *testing.T) {
    87  	t.Parallel()
    88  
    89  	db, err := sqlite3.Open(":memory:")
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	defer db.Close()
    94  
    95  	pivot.Register(db)
    96  
    97  	err = db.Exec(`
    98  		CREATE TABLE r AS
    99  			SELECT 1 id UNION SELECT 2 UNION SELECT 3;
   100  
   101  		CREATE TABLE c(
   102  			id   INTEGER PRIMARY KEY,
   103  			name TEXT
   104  		);
   105  		INSERT INTO c (name) VALUES
   106  			('a'),('b'),('c'),('d');
   107  
   108  		CREATE TABLE x(
   109  			r_id INT,
   110  			c_id INT,
   111  			val  TEXT
   112  		);
   113  		INSERT INTO x (r_id, c_id, val)
   114  			SELECT r.id, c.id, c.name || r.id
   115  			FROM c, r;				
   116  	`)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  
   121  	err = db.Exec(`
   122  		CREATE VIRTUAL TABLE v_x USING pivot(
   123  			-- rows
   124  			(SELECT id r_id FROM r),
   125  			-- columns
   126  			(SELECT id c_id, name FROM c),
   127  			-- cells
   128  			(SELECT val FROM x WHERE r_id = ?1 AND c_id = ?2)
   129  		)`)
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  
   134  	stmt, _, err := db.Prepare(`SELECT * FROM v_x WHERE rowid <> 0 AND r_id <> 1 ORDER BY rowid, r_id DESC LIMIT 1`)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	defer stmt.Close()
   139  
   140  	if stmt.Step() {
   141  		if got := stmt.ColumnInt(0); got != 3 {
   142  			t.Errorf("got %d, want 3", got)
   143  		}
   144  	}
   145  }
   146  
   147  func TestRegister_errors(t *testing.T) {
   148  	t.Parallel()
   149  
   150  	db, err := sqlite3.Open(":memory:")
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	defer db.Close()
   155  
   156  	pivot.Register(db)
   157  
   158  	err = db.Exec(`CREATE VIRTUAL TABLE pivot USING pivot()`)
   159  	if err == nil {
   160  		t.Fatal("want error")
   161  	} else {
   162  		t.Log(err)
   163  	}
   164  
   165  	err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot(SELECT 1, SELECT 2, SELECT 3)`)
   166  	if err == nil {
   167  		t.Fatal("want error")
   168  	} else {
   169  		t.Log(err)
   170  	}
   171  
   172  	err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), SELECT 2, SELECT 3)`)
   173  	if err == nil {
   174  		t.Fatal("want error")
   175  	} else {
   176  		t.Log(err)
   177  	}
   178  
   179  	err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 2), SELECT 3)`)
   180  	if err == nil {
   181  		t.Fatal("want error")
   182  	} else {
   183  		t.Log(err)
   184  	}
   185  
   186  	err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), SELECT 3)`)
   187  	if err == nil {
   188  		t.Fatal("want error")
   189  	} else {
   190  		t.Log(err)
   191  	}
   192  
   193  	err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), (SELECT 3, 4))`)
   194  	if err == nil {
   195  		t.Fatal("want error")
   196  	} else {
   197  		t.Log(err)
   198  	}
   199  
   200  	err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), (SELECT 3))`)
   201  	if err == nil {
   202  		t.Fatal("want error")
   203  	} else {
   204  		t.Log(err)
   205  	}
   206  }
   207  
   208  func pretty(cols []string) string {
   209  	var buf strings.Builder
   210  	for i, s := range cols {
   211  		if i != 0 {
   212  			buf.WriteByte(' ')
   213  		}
   214  		for buf.Len()%8 != 0 {
   215  			buf.WriteByte(' ')
   216  		}
   217  		buf.WriteString(s)
   218  	}
   219  	return buf.String()
   220  }