#  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
#  ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
#  ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
#  ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
#  ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
#  ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
#  ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
#  ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
#  ┃ This file is part of the Perspective library, distributed under the terms ┃
#  ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
#  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

from datetime import date, datetime
import numpy as np
import pandas as pd
from pytest import raises, mark
import perspective as psp

client = psp.Server().new_local_client()
Table = client.table


@mark.skip(reason="We do not support numpy types in the Table constructor")
class TestTableNumpy(object):
    def test_empty_table(self):
        tbl = Table([])
        assert tbl.size() == 0

    def test_table_int(self):
        data = {"a": np.array([1, 2, 3]), "b": np.array([4, 5, 6])}
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {"a": [1, 2, 3], "b": [4, 5, 6]}

    def test_table_int_lots_of_columns(self):
        data = {
            "a": np.array([1, 2, 3]),
            "b": np.array([4, 5, 6]),
            "c": np.array([4, 5, 6]),
            "d": np.array([4, 5, 6]),
            "e": np.array([4, 5, 6]),
            "f": np.array([4, 5, 6]),
        }
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {
            "a": [1, 2, 3],
            "b": [4, 5, 6],
            "c": [4, 5, 6],
            "d": [4, 5, 6],
            "e": [4, 5, 6],
            "f": [4, 5, 6],
        }

    def test_table_int_with_None(self):
        data = {
            "a": np.array([1, 2, 3, None, None]),
            "b": np.array([4, 5, 6, None, None]),
        }
        tbl = Table(data)
        assert tbl.size() == 5
        assert tbl.view().to_columns() == {
            "a": [1, 2, 3, None, None],
            "b": [4, 5, 6, None, None],
        }

    def test_table_int8(self):
        data = {
            "a": np.array([1, 2, 3]).astype(np.int8),
            "b": np.array([4, 5, 6]).astype(np.int8),
        }
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {"a": [1, 2, 3], "b": [4, 5, 6]}

    def test_table_int16(self):
        data = {
            "a": np.array([1, 2, 3]).astype(np.int16),
            "b": np.array([4, 5, 6]).astype(np.int16),
        }
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {"a": [1, 2, 3], "b": [4, 5, 6]}

    def test_table_int32(self):
        data = {
            "a": np.array([1, 2, 3]).astype(np.int32),
            "b": np.array([4, 5, 6]).astype(np.int32),
        }
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {"a": [1, 2, 3], "b": [4, 5, 6]}

    def test_table_int64(self):
        data = {
            "a": np.array([1, 2, 3]).astype(np.int64),
            "b": np.array([4, 5, 6]).astype(np.int64),
        }
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {"a": [1, 2, 3], "b": [4, 5, 6]}

    def test_table_float(self):
        data = {"a": np.array([1.1, 2.2]), "b": np.array([3.3, 4.4])}
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {"a": [1.1, 2.2], "b": [3.3, 4.4]}

    def test_table_float32(self):
        data = {
            "a": np.array([1.1, 2.2]).astype(np.float32),
            "b": np.array([3.3, 4.4]).astype(np.float32),
        }
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {
            # py::cast automatically upcasts to 64-bit float
            "a": [1.100000023841858, 2.200000047683716],
            "b": [3.299999952316284, 4.400000095367432],
        }

    def test_table_float64(self):
        data = {
            "a": np.array([1.1, 2.2]).astype(np.float64),
            "b": np.array([3.3, 4.4]).astype(np.float64),
        }
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {"a": [1.1, 2.2], "b": [3.3, 4.4]}

    # booleans

    def test_table_bool(self):
        data = {"a": np.array([True, False]), "b": np.array([False, True])}
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {"a": [True, False], "b": [False, True]}

    def test_table_bool8(self):
        data = {
            "a": np.array([True, False]).astype(np.bool8),
            "b": np.array([False, True]).astype(np.bool8),
        }
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {"a": [True, False], "b": [False, True]}

    def test_table_bool_with_none(self):
        data = {
            "a": np.array([True, False, None, False]),
            "b": np.array([False, True, None, False]),
        }
        tbl = Table(data)
        assert tbl.size() == 4
        assert tbl.view().to_columns() == {
            "a": [True, False, None, False],
            "b": [False, True, None, False],
        }

    def test_table_bool_with_dtype(self):
        data = {
            "a": np.array([True, False, False], dtype="?"),
            "b": np.array([False, True, False], dtype="?"),
        }
        tbl = Table(data)
        assert tbl.size() == 3
        assert tbl.view().to_columns() == {
            "a": [True, False, False],
            "b": [False, True, False],
        }

    def test_table_bool_str(self):
        data = {"a": np.array(["True", "False"]), "b": np.array(["False", "True"])}
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.schema() == {"a": bool, "b": "boolean"}
        assert tbl.view().to_columns() == {"a": [True, False], "b": [False, True]}

    # strings

    def test_table_str_object(self):
        data = {
            "a": np.array(["abc", "def"], dtype=object),
            "b": np.array(["hij", "klm"], dtype=object),
        }
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {"a": ["abc", "def"], "b": ["hij", "klm"]}

    def test_table_str_dtype(self):
        dtype = "U3"
        data = {
            "a": np.array(["abc", "def"], dtype=dtype),
            "b": np.array(["hij", "klm"], dtype=dtype),
        }
        tbl = Table(data)
        assert tbl.size() == 2
        assert tbl.view().to_columns() == {"a": ["abc", "def"], "b": ["hij", "klm"]}

    # date and datetime

    def test_table_date(self):
        data = {"a": np.array([date(2019, 7, 11)]), "b": np.array([date(2019, 7, 12)])}
        tbl = Table(data)
        assert tbl.size() == 1
        assert tbl.schema() == {"a": date, "b": "date"}
        assert tbl.view().to_columns() == {
            "a": [datetime(2019, 7, 11)],
            "b": [datetime(2019, 7, 12)],
        }

    def test_table_np_datetime(self):
        data = {
            "a": np.array([datetime(2019, 7, 11, 12, 13)], dtype="datetime64[ns]"),
            "b": np.array([datetime(2019, 7, 11, 12, 14)], dtype="datetime64[ns]"),
        }
        tbl = Table(data)
        assert tbl.size() == 1
        assert tbl.schema() == {"a": datetime, "b": "datetime"}
        assert tbl.view().to_numpy() == {
            "a": np.array([datetime(2019, 7, 11, 12, 13)], dtype=object),
            "b": np.array([datetime(2019, 7, 11, 12, 14)], dtype=object),
        }

    def test_table_np_datetime_mixed_dtype(self):
        data = {
            "a": np.array([datetime(2019, 7, 11, 12, 13)], dtype="datetime64[ns]"),
            "b": np.array([datetime(2019, 7, 11, 12, 14)], dtype=object),
        }
        tbl = Table(data)
        assert tbl.size() == 1
        assert tbl.schema() == {"a": datetime, "b": "datetime"}
        assert tbl.view().to_numpy() == {
            "a": np.array([datetime(2019, 7, 11, 12, 13)], dtype=object),
            "b": np.array([datetime(2019, 7, 11, 12, 14)], dtype=object),
        }

    def test_table_np_datetime_default(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[ns]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_string_dtype(self):
        data = ["2019/07/11 15:30:05", "2019/07/11 15:30:05"]
        tbl = Table({"a": np.array(data)})

        assert tbl.view().to_columns() == {
            "a": [datetime(2019, 7, 11, 15, 30, 5), datetime(2019, 7, 11, 15, 30, 5)]
        }

    def test_table_np_datetime_string_on_schema(self):
        data = ["2019/07/11 15:30:05", "2019/07/11 15:30:05"]
        tbl = Table({"a": "datetime"})

        tbl.update({"a": data})

        assert tbl.view().to_columns() == {
            "a": [datetime(2019, 7, 11, 15, 30, 5), datetime(2019, 7, 11, 15, 30, 5)]
        }

    def test_table_np_datetime_ns(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[ns]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_us(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[us]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_ms(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[ms]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_s(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[s]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_m(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[m]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_h(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[h]")}
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))]
        }

    def test_table_np_datetime_D(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[D]")}
        )

        assert tbl.schema() == {"a": "date"}

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 0, 0))]
        }

    def test_table_np_datetime_W(self, util):
        tbl = Table(
            {"a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[W]")}
        )

        assert tbl.schema() == {"a": "date"}

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 11, 0, 0))]
        }

    def test_table_np_datetime_M(self):
        tbl = Table(
            {
                "a": np.array(
                    [
                        datetime(2019, 5, 12, 11, 0),
                        datetime(2019, 6, 12, 11, 0),
                        datetime(2019, 7, 12, 11, 0),
                    ],
                    dtype="datetime64[M]",
                )
            }
        )

        assert tbl.schema() == {"a": "date"}

        assert tbl.view().to_columns() == {
            "a": [
                datetime(2019, 5, 1, 0, 0),
                datetime(2019, 6, 1, 0, 0),
                datetime(2019, 7, 1, 0, 0),
            ]
        }

    def test_table_np_datetime_Y(self):
        tbl = Table(
            {
                "a": np.array(
                    [
                        datetime(2017, 5, 12, 11, 0),
                        datetime(2018, 6, 12, 11, 0),
                        datetime(2019, 7, 12, 11, 0),
                    ],
                    dtype="datetime64[Y]",
                )
            }
        )

        assert tbl.schema() == {"a": "date"}

        assert tbl.view().to_columns() == {
            "a": [
                datetime(2017, 1, 1, 0, 0),
                datetime(2018, 1, 1, 0, 0),
                datetime(2019, 1, 1, 0, 0),
            ]
        }

    def test_table_np_datetime_ms_nat(self, util):
        tbl = Table(
            {
                "a": np.array(
                    [datetime(2019, 7, 12, 11, 0), np.datetime64("nat")],
                    dtype="datetime64[ms]",
                )
            }
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0)), None]
        }

    def test_table_np_datetime_s_nat(self, util):
        tbl = Table(
            {
                "a": np.array(
                    [datetime(2019, 7, 12, 11, 0), np.datetime64("nat")],
                    dtype="datetime64[s]",
                )
            }
        )

        assert tbl.view().to_columns() == {
            "a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0)), None]
        }

    def test_table_np_timedelta(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[ns]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[ns]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["950400000000000 nanoseconds"]}

    def test_table_np_timedelta_us(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[us]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[us]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["950400000000 microseconds"]}

    def test_table_np_timedelta_ms(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[ms]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[ms]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["950400000 milliseconds"]}

    def test_table_np_timedelta_s(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[s]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[s]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["950400 seconds"]}

    def test_table_np_timedelta_m(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[m]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[m]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["15840 minutes"]}

    def test_table_np_timedelta_h(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[h]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[h]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["264 hours"]}

    def test_table_np_timedelta_d(self):
        tbl = Table(
            {
                "a": np.array([datetime(2019, 7, 12, 11, 0)], dtype="datetime64[D]")
                - np.array([datetime(2019, 7, 1, 11, 0)], dtype="datetime64[D]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": ["11 days"]}

    def test_table_np_timedelta_with_none(self):
        tbl = Table(
            {
                "a": np.array(
                    [None, datetime(2019, 7, 12, 11, 0)], dtype="datetime64[ns]"
                )
                - np.array([datetime(2019, 7, 1, 11, 0), None], dtype="datetime64[ns]")
            }
        )

        assert tbl.schema() == {"a": "string"}

        assert tbl.view().to_columns() == {"a": [None, None]}  # two `NaT` values

    def test_table_np_mixed(self):
        data = {
            "a": np.arange(5),
            "b": np.full(5, np.nan),
            "c": ["a", "b", "c", "d", "e"],
        }

        # should not be able to parse mixed dicts of numpy array with list
        with raises(psp.PerspectiveError):
            Table(data)

    def test_table_np_promote(self):
        data = {
            "a": np.arange(5),
            "b": np.full(5, np.nan),
            "c": np.array([1, 2, 3, 2147483648, 5]),
        }
        tbl = Table({"a": "integer", "b": "float", "c": "integer"})
        tbl.update(data)
        assert tbl.size() == 5
        assert tbl.schema() == {"a": "integer", "b": "float", "c": "integer"}

        assert tbl.view().to_columns() == {
            "a": [0, 1, 2, 3, 4],
            "b": [None, None, None, None, None],
            "c": [1.0, 2.0, 3.0, 2147483648.0, 5.0],
        }

    def test_table_np_promote_to_string(self):
        data = {
            "a": np.arange(4),
            "b": np.array([1, 2, "abc", "abc"]),
        }
        tbl = Table(data)
        assert tbl.size() == 4
        assert tbl.schema() == {
            "a": "integer",
            "b": "string",
        }

        assert tbl.view().to_columns() == {
            "a": [0, 1, 2, 3],
            "b": ["1", "2", "abc", "abc"],
        }

    def test_table_np_implicit_index(self):
        data = {
            "a": np.array(["a", "b", "c", "d", "e"]),
            "b": np.array([1, 2, 3, 4, 5]),
        }
        tbl = Table(data)
        assert tbl.size() == 5
        assert tbl.schema() == {"a": "string", "b": "integer"}
        tbl.update(
            {
                "__INDEX__": np.array([1, 2, 3, 4]),
                "a": np.array(["bb", "cc", "dd", "ee"]),
            }
        )

        assert tbl.view().to_columns() == {
            "a": ["a", "bb", "cc", "dd", "ee"],
            "b": [1, 2, 3, 4, 5],
        }

    # from schema

    def test_table_numpy_from_schema_int(self):
        df = {"a": np.array([1, None, 2, None, 3, 4])}
        table = Table({"a": "integer"})
        table.update(df)
        assert table.view().to_columns()["a"] == [1, None, 2, None, 3, 4]

    def test_table_numpy_from_schema_bool(self):
        data = [True, False, True, False]
        df = {"a": data}
        table = Table({"a": "boolean"})
        table.update(df)
        assert table.view().to_columns()["a"] == data

    def test_table_numpy_from_schema_float(self):
        data = [1.5, None, 2.5, None, 3.5, 4.5]
        df = {"a": np.array(data)}
        table = Table({"a": "float"})
        table.update(df)
        assert table.view().to_columns()["a"] == data

    def test_table_numpy_from_schema_float_all_nan(self):
        data = [np.nan, np.nan, np.nan, np.nan]
        df = {"a": np.array(data)}
        table = Table({"a": "float"})
        table.update(df)
        assert table.view().to_columns()["a"] == [None, None, None, None]

    def test_table_numpy_from_schema_float_to_int(self):
        data = [None, 1.5, None, 2.5, None, 3.5, 4.5]
        df = {"a": np.array(data)}
        table = Table({"a": "integer"})
        table.update(df)
        # truncates decimal
        assert table.view().to_columns()["a"] == [None, 1, None, 2, None, 3, 4]

    def test_table_numpy_from_schema_float_to_int_with_nan(self):
        df = {"a": np.array([np.nan, 1.5, np.nan, 2.5, np.nan, 3.5, 4.5])}
        table = Table({"a": "integer"})
        table.update(df)
        # truncates decimal
        assert table.view().to_columns()["a"] == [None, 1, None, 2, None, 3, 4]

    def test_table_numpy_from_schema_float_to_int_with_nan_partial(self):
        df = {"a": np.array([np.nan, 1.5, np.nan, 2.5, np.nan, 3.5, 4.5])}
        table = Table({"a": "integer", "b": "integer"})
        table.update(df)
        assert table.size() == 7
        # truncates decimal
        assert table.view().to_columns() == {
            "a": [None, 1, None, 2, None, 3, 4],
            "b": [None, None, None, None, None, None, None],
        }

    def test_table_numpy_from_schema_float_to_int_with_nan_partial_indexed(self):
        """Assert that the null masking works even when primary keys
        are being reordered."""
        df = {
            "a": np.array([np.nan, 1.5, np.nan, 2.5, np.nan, 3.5, 4.5]),
            "b": np.array([1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]),
        }
        table = Table({"a": "integer", "b": "integer"}, index="b")
        table.update(df)

        # truncates decimal
        assert table.view().to_columns() == {
            "a": [None, 1, None, 2, None, 3, 4],
            "b": [1, 2, 3, 4, 5, 6, 7],
        }

        table.update(pd.DataFrame({"a": [10, 9, 8], "b": [2, 3, 5]}))

        assert table.view().to_columns() == {
            "a": [None, 10, 9, 2, 8, 3, 4],
            "b": [1, 2, 3, 4, 5, 6, 7],
        }

        table.update(
            {
                "a": np.array([100, np.nan], dtype=np.float64),
                "b": np.array([-1, 6], dtype=np.float64),
            }
        )

        assert table.view().to_columns() == {
            "a": [100, None, 10, 9, 2, 8, None, 4],
            "b": [-1, 1, 2, 3, 4, 5, 6, 7],
        }

        table.update(
            {
                "a": np.array([100, 1000, np.nan], dtype=np.float64),
                "b": np.array([100, 6, 97], dtype=np.float64),
            }
        )

        assert table.view().to_columns() == {
            "a": [100, None, 10, 9, 2, 8, 1000, 4, None, 100],
            "b": [-1, 1, 2, 3, 4, 5, 6, 7, 97, 100],
        }

    def test_table_numpy_from_schema_int_to_float(self):
        data = [None, 1, None, 2, None, 3, 4]
        df = {"a": np.array(data)}
        table = Table({"a": "float"})
        table.update(df)
        assert table.view().to_columns()["a"] == [None, 1.0, None, 2.0, None, 3.0, 4.0]

    def test_table_numpy_from_schema_date(self):
        data = [date(2019, 8, 15), None, date(2019, 8, 16)]
        df = {"a": np.array(data)}
        table = Table({"a": "date"})
        table.update(df)
        assert table.view().to_columns()["a"] == [
            datetime(2019, 8, 15),
            None,
            datetime(2019, 8, 16),
        ]

    def test_table_numpy_from_schema_datetime(self):
        data = [
            datetime(2019, 7, 11, 12, 30, 5),
            None,
            datetime(2019, 7, 11, 13, 30, 5),
            None,
        ]
        df = {"a": np.array(data)}
        table = Table({"a": "datetime"})
        table.update(df)
        assert table.view().to_columns()["a"] == data

    def test_table_numpy_from_schema_datetime_timestamp_s(self, util):
        data = [
            util.to_timestamp(datetime(2019, 7, 11, 12, 30, 5)),
            np.nan,
            util.to_timestamp(datetime(2019, 7, 11, 13, 30, 5)),
            np.nan,
        ]
        df = {"a": np.array(data)}
        table = Table({"a": "datetime"})
        table.update(df)
        assert table.view().to_columns()["a"] == [
            datetime(2019, 7, 11, 12, 30, 5),
            None,
            datetime(2019, 7, 11, 13, 30, 5),
            None,
        ]

    def test_table_numpy_from_schema_datetime_timestamp_ms(self, util):
        data = [
            util.to_timestamp(datetime(2019, 7, 11, 12, 30, 5)) * 1000,
            np.nan,
            util.to_timestamp(datetime(2019, 7, 11, 13, 30, 5)) * 1000,
            np.nan,
        ]

        df = {"a": np.array(data)}
        table = Table({"a": "datetime"})
        table.update(df)
        assert table.view().to_columns()["a"] == [
            datetime(2019, 7, 11, 12, 30, 5),
            None,
            datetime(2019, 7, 11, 13, 30, 5),
            None,
        ]

    def test_table_numpy_from_schema_str(self):
        data = ["a", None, "b", None, "c"]
        df = {"a": np.array(data)}
        table = Table({"a": "string"})
        table.update(df)
        assert table.view().to_columns()["a"] == data

    # partial update

    def test_table_numpy_partial_update(self):
        data = ["a", None, "b", None, "c"]
        df = {"a": np.array([1, 2, 3, 4, 5]), "b": np.array(data), "c": np.array(data)}
        table = Table(df, index="a")
        table.update({"a": np.array([2, 4, 5]), "b": np.array(["x", "y", "z"])})
        assert table.view().to_columns() == {
            "a": [1, 2, 3, 4, 5],
            "b": ["a", "x", "b", "y", "z"],
            "c": ["a", None, "b", None, "c"],
        }

    def test_table_numpy_partial_update_implicit(self):
        data = ["a", None, "b", None, "c"]
        df = {"a": np.array([1, 2, 3, 4, 5]), "b": np.array(data), "c": np.array(data)}
        table = Table(df)
        table.update({"__INDEX__": np.array([1, 3, 4]), "b": np.array(["x", "y", "z"])})
        assert table.view().to_columns() == {
            "a": [1, 2, 3, 4, 5],
            "b": ["a", "x", "b", "y", "z"],
            "c": ["a", None, "b", None, "c"],
        }

    # structured array

    def test_table_structured_array(self):
        d = np.array([(1.0, 2), (3.0, 4)], dtype=[("x", "<f8"), ("y", "<i8")])
        table = Table(d)
        assert table.schema() == {"x": "float", "y": "integer"}
        assert table.view().to_columns() == {"x": [1.0, 3.0], "y": [2, 4]}

    # recarray

    def test_table_recarray(self):
        d = np.array([(1.0, 2), (3.0, 4)], dtype=[("x", "<f8"), ("y", "<i8")]).view(
            np.recarray
        )
        table = Table(d)
        assert table.schema() == {"x": "float", "y": "integer"}
        assert table.view().to_columns() == {"x": [1.0, 3.0], "y": [2, 4]}

    def test_table_recarray_datetime_ns(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[ns]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "datetime", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 8, 30, 29), datetime(2019, 7, 11, 8, 30, 29)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_us(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[us]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "datetime", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 8, 30, 29), datetime(2019, 7, 11, 8, 30, 29)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_ms(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[ms]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "datetime", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 8, 30, 29), datetime(2019, 7, 11, 8, 30, 29)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_s(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[s]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "datetime", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 8, 30, 29), datetime(2019, 7, 11, 8, 30, 29)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_m(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 31, 29), 4),
            ],
            dtype=[("x", "datetime64[m]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "datetime", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 8, 30, 0), datetime(2019, 7, 11, 8, 31, 0)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_h(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 9, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[h]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "datetime", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 8, 0, 0), datetime(2019, 7, 11, 9, 0, 0)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_D(self):
        d = np.array(
            [
                (datetime(2019, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 12, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[D]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "date", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 7, 11, 0, 0, 0), datetime(2019, 7, 12, 0, 0, 0)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_W(self):
        d = np.array(
            [(datetime(2019, 6, 30, 0, 0, 0), 2), (datetime(2019, 7, 7, 0, 0, 0), 4)],
            dtype=[("x", "datetime64[W]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "date", "y": "integer"}
        assert table.view().to_columns() == {
            # one week apart
            "x": [datetime(2019, 6, 27, 0, 0, 0), datetime(2019, 7, 4, 0, 0, 0)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_M(self):
        d = np.array(
            [
                (datetime(2019, 6, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[M]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "date", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2019, 6, 1, 0, 0, 0), datetime(2019, 7, 1, 0, 0, 0)],
            "y": [2, 4],
        }

    def test_table_recarray_datetime_Y(self):
        d = np.array(
            [
                (datetime(2018, 7, 11, 8, 30, 29), 2),
                (datetime(2019, 7, 11, 8, 30, 29), 4),
            ],
            dtype=[("x", "datetime64[Y]"), ("y", "<i8")],
        ).view(np.recarray)
        table = Table(d)
        assert table.schema() == {"x": "date", "y": "integer"}
        assert table.view().to_columns() == {
            "x": [datetime(2018, 1, 1, 0, 0, 0), datetime(2019, 1, 1, 0, 0, 0)],
            "y": [2, 4],
        }

    def test_table_recarray_str(self):
        table = Table(
            np.array(
                [("string1", "string2"), ("string3", "string4")],
                dtype=[("x", "O"), ("y", "O")],
            ).view(np.recarray)
        )
        assert table.schema() == {"x": "string", "y": "string"}
        assert table.view().to_columns() == {
            "x": ["string1", "string3"],
            "y": ["string2", "string4"],
        }

    def test_table_recarray_str_dtype(self):
        dtype = "U7"
        table = Table(
            np.array(
                [("string1", "string2"), ("string3", "string4")],
                dtype=[("x", dtype), ("y", dtype)],
            ).view(np.recarray)
        )
        assert table.schema() == {"x": "string", "y": "string"}
        assert table.view().to_columns() == {
            "x": ["string1", "string3"],
            "y": ["string2", "string4"],
        }

    def test_table_float32_to_float64(self):
        data = {
            "a": np.array([1.1, 2.2]).astype(np.float32),
            "b": np.array([3.3, 4.4]).astype(np.float32),
        }
        table = Table(data)
        assert table.size() == 2
        schema = table.schema()
        assert schema == {"a": "float", "b": "float"}
        assert table.view().to_columns() == {
            "a": [1.100000023841858, 2.200000047683716],
            "b": [3.299999952316284, 4.400000095367432],
        }

    def test_table_float64_to_float32(self):
        data = {
            "a": np.array([1.1, 2.2]).astype(np.float64),
            "b": np.array([3.3, 4.4]).astype(np.float64),
        }
        table = Table(data)
        assert table.size() == 2
        schema = table.schema()
        assert schema == {"a": "float", "b": "float"}
        view = table.view()
        assert view.to_columns() == {"a": [1.1, 2.2], "b": [3.3, 4.4]}
        view.schema({"a": np.float32, "b": np.float32})
        assert view.to_columns() == {"a": [1.1, 2.2], "b": [3.3, 4.4]}
        data["a"] = data["a"].astype(np.float32)
        data["b"] = data["b"].astype(np.float32)
        table = Table(data)
        assert table.size() == 2
        schema = table.schema()
        assert schema == {"a": "float", "b": "float"}
        view = table.view()
        assert view.to_columns() == {
            "a": [1.100000023841858, 2.200000047683716],
            "b": [3.299999952316284, 4.400000095367432],
        }

    def test_table_float32_to_float64_with_nulls(self):
        data = {
            "a": np.array([1.1, np.nan, 2.2]).astype(np.float32),
            "b": np.array([3.3, 4.4, np.nan]).astype(np.float32),
        }
        table = Table(data)
        assert table.size() == 3
        schema = table.schema()
        assert schema == {"a": "float", "b": "float"}
        assert table.view().to_columns() == {
            "a": [1.100000023841858, None, 2.200000047683716],
            "b": [3.299999952316284, 4.400000095367432, None],
        }
