passagemath-categories 10.6.32__cp314-cp314t-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_categories-10.6.32.dist-info/METADATA +156 -0
- passagemath_categories-10.6.32.dist-info/RECORD +719 -0
- passagemath_categories-10.6.32.dist-info/WHEEL +5 -0
- passagemath_categories-10.6.32.dist-info/top_level.txt +2 -0
- passagemath_categories.libs/libgcc_s-2d945d6c.so.1 +0 -0
- passagemath_categories.libs/libgmp-28992bcb.so.10.5.0 +0 -0
- passagemath_categories.libs/libstdc++-85f2cd6d.so.6.0.33 +0 -0
- sage/all__sagemath_categories.py +28 -0
- sage/arith/all.py +38 -0
- sage/arith/constants.pxd +27 -0
- sage/arith/functions.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/functions.pxd +4 -0
- sage/arith/functions.pyx +221 -0
- sage/arith/misc.py +6552 -0
- sage/arith/multi_modular.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/multi_modular.pxd +39 -0
- sage/arith/multi_modular.pyx +994 -0
- sage/arith/rational_reconstruction.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/rational_reconstruction.pxd +4 -0
- sage/arith/rational_reconstruction.pyx +115 -0
- sage/arith/srange.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/srange.pyx +571 -0
- sage/calculus/all__sagemath_categories.py +2 -0
- sage/calculus/functional.py +481 -0
- sage/calculus/functions.py +151 -0
- sage/categories/additive_groups.py +73 -0
- sage/categories/additive_magmas.py +1044 -0
- sage/categories/additive_monoids.py +114 -0
- sage/categories/additive_semigroups.py +184 -0
- sage/categories/affine_weyl_groups.py +238 -0
- sage/categories/algebra_ideals.py +95 -0
- sage/categories/algebra_modules.py +96 -0
- sage/categories/algebras.py +349 -0
- sage/categories/algebras_with_basis.py +377 -0
- sage/categories/all.py +160 -0
- sage/categories/aperiodic_semigroups.py +29 -0
- sage/categories/associative_algebras.py +47 -0
- sage/categories/bialgebras.py +101 -0
- sage/categories/bialgebras_with_basis.py +414 -0
- sage/categories/bimodules.py +206 -0
- sage/categories/chain_complexes.py +268 -0
- sage/categories/classical_crystals.py +480 -0
- sage/categories/coalgebras.py +405 -0
- sage/categories/coalgebras_with_basis.py +232 -0
- sage/categories/coercion_methods.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/categories/coercion_methods.pyx +52 -0
- sage/categories/commutative_additive_groups.py +104 -0
- sage/categories/commutative_additive_monoids.py +45 -0
- sage/categories/commutative_additive_semigroups.py +48 -0
- sage/categories/commutative_algebra_ideals.py +87 -0
- sage/categories/commutative_algebras.py +94 -0
- sage/categories/commutative_ring_ideals.py +58 -0
- sage/categories/commutative_rings.py +736 -0
- sage/categories/complete_discrete_valuation.py +293 -0
- sage/categories/complex_reflection_groups.py +145 -0
- sage/categories/complex_reflection_or_generalized_coxeter_groups.py +1249 -0
- sage/categories/coxeter_group_algebras.py +186 -0
- sage/categories/coxeter_groups.py +3402 -0
- sage/categories/crystals.py +2628 -0
- sage/categories/cw_complexes.py +216 -0
- sage/categories/dedekind_domains.py +137 -0
- sage/categories/discrete_valuation.py +325 -0
- sage/categories/distributive_magmas_and_additive_magmas.py +100 -0
- sage/categories/division_rings.py +114 -0
- sage/categories/domains.py +95 -0
- sage/categories/drinfeld_modules.py +789 -0
- sage/categories/dual.py +42 -0
- sage/categories/enumerated_sets.py +1146 -0
- sage/categories/euclidean_domains.py +271 -0
- sage/categories/examples/algebras_with_basis.py +102 -0
- sage/categories/examples/all.py +1 -0
- sage/categories/examples/commutative_additive_monoids.py +130 -0
- sage/categories/examples/commutative_additive_semigroups.py +199 -0
- sage/categories/examples/coxeter_groups.py +8 -0
- sage/categories/examples/crystals.py +236 -0
- sage/categories/examples/cw_complexes.py +163 -0
- sage/categories/examples/facade_sets.py +187 -0
- sage/categories/examples/filtered_algebras_with_basis.py +204 -0
- sage/categories/examples/filtered_modules_with_basis.py +154 -0
- sage/categories/examples/finite_coxeter_groups.py +252 -0
- sage/categories/examples/finite_dimensional_algebras_with_basis.py +148 -0
- sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py +495 -0
- sage/categories/examples/finite_enumerated_sets.py +208 -0
- sage/categories/examples/finite_monoids.py +150 -0
- sage/categories/examples/finite_semigroups.py +190 -0
- sage/categories/examples/finite_weyl_groups.py +191 -0
- sage/categories/examples/graded_connected_hopf_algebras_with_basis.py +152 -0
- sage/categories/examples/graded_modules_with_basis.py +168 -0
- sage/categories/examples/graphs.py +122 -0
- sage/categories/examples/hopf_algebras_with_basis.py +145 -0
- sage/categories/examples/infinite_enumerated_sets.py +190 -0
- sage/categories/examples/lie_algebras.py +352 -0
- sage/categories/examples/lie_algebras_with_basis.py +196 -0
- sage/categories/examples/magmas.py +162 -0
- sage/categories/examples/manifolds.py +94 -0
- sage/categories/examples/monoids.py +144 -0
- sage/categories/examples/posets.py +178 -0
- sage/categories/examples/semigroups.py +580 -0
- sage/categories/examples/semigroups_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/categories/examples/semigroups_cython.pyx +221 -0
- sage/categories/examples/semirings.py +249 -0
- sage/categories/examples/sets_cat.py +706 -0
- sage/categories/examples/sets_with_grading.py +101 -0
- sage/categories/examples/with_realizations.py +542 -0
- sage/categories/fields.py +991 -0
- sage/categories/filtered_algebras.py +63 -0
- sage/categories/filtered_algebras_with_basis.py +548 -0
- sage/categories/filtered_hopf_algebras_with_basis.py +138 -0
- sage/categories/filtered_modules.py +210 -0
- sage/categories/filtered_modules_with_basis.py +1209 -0
- sage/categories/finite_complex_reflection_groups.py +1506 -0
- sage/categories/finite_coxeter_groups.py +1138 -0
- sage/categories/finite_crystals.py +103 -0
- sage/categories/finite_dimensional_algebras_with_basis.py +1860 -0
- sage/categories/finite_dimensional_bialgebras_with_basis.py +33 -0
- sage/categories/finite_dimensional_coalgebras_with_basis.py +33 -0
- sage/categories/finite_dimensional_graded_lie_algebras_with_basis.py +231 -0
- sage/categories/finite_dimensional_hopf_algebras_with_basis.py +38 -0
- sage/categories/finite_dimensional_lie_algebras_with_basis.py +2774 -0
- sage/categories/finite_dimensional_modules_with_basis.py +1407 -0
- sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +167 -0
- sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +270 -0
- sage/categories/finite_enumerated_sets.py +769 -0
- sage/categories/finite_fields.py +252 -0
- sage/categories/finite_groups.py +256 -0
- sage/categories/finite_lattice_posets.py +242 -0
- sage/categories/finite_monoids.py +316 -0
- sage/categories/finite_permutation_groups.py +339 -0
- sage/categories/finite_posets.py +1994 -0
- sage/categories/finite_semigroups.py +136 -0
- sage/categories/finite_sets.py +93 -0
- sage/categories/finite_weyl_groups.py +39 -0
- sage/categories/finitely_generated_lambda_bracket_algebras.py +112 -0
- sage/categories/finitely_generated_lie_conformal_algebras.py +114 -0
- sage/categories/finitely_generated_magmas.py +57 -0
- sage/categories/finitely_generated_semigroups.py +214 -0
- sage/categories/function_fields.py +76 -0
- sage/categories/g_sets.py +77 -0
- sage/categories/gcd_domains.py +65 -0
- sage/categories/generalized_coxeter_groups.py +94 -0
- sage/categories/graded_algebras.py +85 -0
- sage/categories/graded_algebras_with_basis.py +258 -0
- sage/categories/graded_bialgebras.py +32 -0
- sage/categories/graded_bialgebras_with_basis.py +32 -0
- sage/categories/graded_coalgebras.py +65 -0
- sage/categories/graded_coalgebras_with_basis.py +51 -0
- sage/categories/graded_hopf_algebras.py +41 -0
- sage/categories/graded_hopf_algebras_with_basis.py +169 -0
- sage/categories/graded_lie_algebras.py +91 -0
- sage/categories/graded_lie_algebras_with_basis.py +44 -0
- sage/categories/graded_lie_conformal_algebras.py +74 -0
- sage/categories/graded_modules.py +133 -0
- sage/categories/graded_modules_with_basis.py +329 -0
- sage/categories/graphs.py +138 -0
- sage/categories/group_algebras.py +430 -0
- sage/categories/groupoid.py +94 -0
- sage/categories/groups.py +667 -0
- sage/categories/h_trivial_semigroups.py +64 -0
- sage/categories/hecke_modules.py +185 -0
- sage/categories/highest_weight_crystals.py +980 -0
- sage/categories/hopf_algebras.py +219 -0
- sage/categories/hopf_algebras_with_basis.py +309 -0
- sage/categories/infinite_enumerated_sets.py +115 -0
- sage/categories/integral_domains.py +203 -0
- sage/categories/j_trivial_semigroups.py +29 -0
- sage/categories/kac_moody_algebras.py +82 -0
- sage/categories/kahler_algebras.py +203 -0
- sage/categories/l_trivial_semigroups.py +63 -0
- sage/categories/lambda_bracket_algebras.py +280 -0
- sage/categories/lambda_bracket_algebras_with_basis.py +107 -0
- sage/categories/lattice_posets.py +89 -0
- sage/categories/left_modules.py +49 -0
- sage/categories/lie_algebras.py +1070 -0
- sage/categories/lie_algebras_with_basis.py +261 -0
- sage/categories/lie_conformal_algebras.py +350 -0
- sage/categories/lie_conformal_algebras_with_basis.py +147 -0
- sage/categories/lie_groups.py +73 -0
- sage/categories/loop_crystals.py +1290 -0
- sage/categories/magmas.py +1189 -0
- sage/categories/magmas_and_additive_magmas.py +149 -0
- sage/categories/magmatic_algebras.py +365 -0
- sage/categories/manifolds.py +352 -0
- sage/categories/matrix_algebras.py +40 -0
- sage/categories/metric_spaces.py +387 -0
- sage/categories/modular_abelian_varieties.py +78 -0
- sage/categories/modules.py +989 -0
- sage/categories/modules_with_basis.py +2794 -0
- sage/categories/monoid_algebras.py +38 -0
- sage/categories/monoids.py +739 -0
- sage/categories/noetherian_rings.py +87 -0
- sage/categories/number_fields.py +242 -0
- sage/categories/ore_modules.py +189 -0
- sage/categories/partially_ordered_monoids.py +49 -0
- sage/categories/permutation_groups.py +63 -0
- sage/categories/pointed_sets.py +42 -0
- sage/categories/polyhedra.py +74 -0
- sage/categories/poor_man_map.py +270 -0
- sage/categories/posets.py +722 -0
- sage/categories/principal_ideal_domains.py +270 -0
- sage/categories/quantum_group_representations.py +543 -0
- sage/categories/quotient_fields.py +728 -0
- sage/categories/r_trivial_semigroups.py +45 -0
- sage/categories/regular_crystals.py +898 -0
- sage/categories/regular_supercrystals.py +170 -0
- sage/categories/right_modules.py +49 -0
- sage/categories/ring_ideals.py +74 -0
- sage/categories/rings.py +1904 -0
- sage/categories/rngs.py +175 -0
- sage/categories/schemes.py +393 -0
- sage/categories/semigroups.py +1060 -0
- sage/categories/semirings.py +71 -0
- sage/categories/semisimple_algebras.py +114 -0
- sage/categories/sets_with_grading.py +235 -0
- sage/categories/shephard_groups.py +43 -0
- sage/categories/signed_tensor.py +120 -0
- sage/categories/simplicial_complexes.py +134 -0
- sage/categories/simplicial_sets.py +1206 -0
- sage/categories/super_algebras.py +149 -0
- sage/categories/super_algebras_with_basis.py +144 -0
- sage/categories/super_hopf_algebras_with_basis.py +126 -0
- sage/categories/super_lie_conformal_algebras.py +193 -0
- sage/categories/super_modules.py +229 -0
- sage/categories/super_modules_with_basis.py +193 -0
- sage/categories/supercommutative_algebras.py +99 -0
- sage/categories/supercrystals.py +406 -0
- sage/categories/tensor.py +110 -0
- sage/categories/topological_spaces.py +170 -0
- sage/categories/triangular_kac_moody_algebras.py +439 -0
- sage/categories/tutorial.py +58 -0
- sage/categories/unique_factorization_domains.py +318 -0
- sage/categories/unital_algebras.py +426 -0
- sage/categories/vector_bundles.py +159 -0
- sage/categories/vector_spaces.py +357 -0
- sage/categories/weyl_groups.py +853 -0
- sage/combinat/all__sagemath_categories.py +34 -0
- sage/combinat/backtrack.py +180 -0
- sage/combinat/combinat.py +2269 -0
- sage/combinat/combinat_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/combinat_cython.pxd +6 -0
- sage/combinat/combinat_cython.pyx +390 -0
- sage/combinat/combination.py +796 -0
- sage/combinat/combinatorial_map.py +416 -0
- sage/combinat/composition.py +2192 -0
- sage/combinat/dlx.py +510 -0
- sage/combinat/integer_lists/__init__.py +7 -0
- sage/combinat/integer_lists/base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/integer_lists/base.pxd +16 -0
- sage/combinat/integer_lists/base.pyx +713 -0
- sage/combinat/integer_lists/invlex.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/integer_lists/invlex.pxd +4 -0
- sage/combinat/integer_lists/invlex.pyx +1650 -0
- sage/combinat/integer_lists/lists.py +328 -0
- sage/combinat/integer_lists/nn.py +48 -0
- sage/combinat/integer_vector.py +1818 -0
- sage/combinat/integer_vector_weighted.py +413 -0
- sage/combinat/matrices/all__sagemath_categories.py +5 -0
- sage/combinat/matrices/dancing_links.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/matrices/dancing_links.pyx +1159 -0
- sage/combinat/matrices/dancing_links_c.h +380 -0
- sage/combinat/matrices/dlxcpp.py +136 -0
- sage/combinat/partition.py +10070 -0
- sage/combinat/partitions.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/partitions.pyx +743 -0
- sage/combinat/permutation.py +10168 -0
- sage/combinat/permutation_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/permutation_cython.pxd +11 -0
- sage/combinat/permutation_cython.pyx +407 -0
- sage/combinat/q_analogues.py +1090 -0
- sage/combinat/ranker.py +268 -0
- sage/combinat/subset.py +1561 -0
- sage/combinat/subsets_hereditary.py +202 -0
- sage/combinat/subsets_pairwise.py +184 -0
- sage/combinat/tools.py +63 -0
- sage/combinat/tuple.py +348 -0
- sage/data_structures/all.py +2 -0
- sage/data_structures/all__sagemath_categories.py +2 -0
- sage/data_structures/binary_matrix.pxd +138 -0
- sage/data_structures/binary_search.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/binary_search.pxd +3 -0
- sage/data_structures/binary_search.pyx +66 -0
- sage/data_structures/bitset.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/bitset.pxd +40 -0
- sage/data_structures/bitset.pyx +2385 -0
- sage/data_structures/bitset_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/bitset_base.pxd +926 -0
- sage/data_structures/bitset_base.pyx +117 -0
- sage/data_structures/bitset_intrinsics.h +487 -0
- sage/data_structures/blas_dict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/blas_dict.pxd +12 -0
- sage/data_structures/blas_dict.pyx +469 -0
- sage/data_structures/list_of_pairs.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/list_of_pairs.pxd +16 -0
- sage/data_structures/list_of_pairs.pyx +122 -0
- sage/data_structures/mutable_poset.py +3312 -0
- sage/data_structures/pairing_heap.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/pairing_heap.h +346 -0
- sage/data_structures/pairing_heap.pxd +88 -0
- sage/data_structures/pairing_heap.pyx +1464 -0
- sage/data_structures/sparse_bitset.pxd +62 -0
- sage/data_structures/stream.py +5070 -0
- sage/databases/all__sagemath_categories.py +7 -0
- sage/databases/sql_db.py +2236 -0
- sage/ext/all__sagemath_categories.py +3 -0
- sage/ext/fast_callable.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/fast_callable.pxd +4 -0
- sage/ext/fast_callable.pyx +2746 -0
- sage/ext/fast_eval.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/fast_eval.pxd +1 -0
- sage/ext/fast_eval.pyx +102 -0
- sage/ext/interpreters/__init__.py +1 -0
- sage/ext/interpreters/all__sagemath_categories.py +2 -0
- sage/ext/interpreters/wrapper_el.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_el.pxd +18 -0
- sage/ext/interpreters/wrapper_el.pyx +148 -0
- sage/ext/interpreters/wrapper_py.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_py.pxd +17 -0
- sage/ext/interpreters/wrapper_py.pyx +133 -0
- sage/functions/airy.py +937 -0
- sage/functions/all.py +97 -0
- sage/functions/bessel.py +2102 -0
- sage/functions/error.py +784 -0
- sage/functions/exp_integral.py +1529 -0
- sage/functions/gamma.py +1087 -0
- sage/functions/generalized.py +672 -0
- sage/functions/hyperbolic.py +747 -0
- sage/functions/hypergeometric.py +1156 -0
- sage/functions/jacobi.py +1705 -0
- sage/functions/log.py +1402 -0
- sage/functions/min_max.py +338 -0
- sage/functions/orthogonal_polys.py +3106 -0
- sage/functions/other.py +2303 -0
- sage/functions/piecewise.py +1505 -0
- sage/functions/prime_pi.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/functions/prime_pi.pyx +262 -0
- sage/functions/special.py +1212 -0
- sage/functions/spike_function.py +278 -0
- sage/functions/transcendental.py +690 -0
- sage/functions/trig.py +1062 -0
- sage/functions/wigner.py +726 -0
- sage/geometry/abc.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/geometry/abc.pyx +82 -0
- sage/geometry/all__sagemath_categories.py +1 -0
- sage/groups/all__sagemath_categories.py +11 -0
- sage/groups/generic.py +1733 -0
- sage/groups/groups_catalog.py +113 -0
- sage/groups/perm_gps/all__sagemath_categories.py +1 -0
- sage/groups/perm_gps/partn_ref/all.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_categories.py +1 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pxd +52 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx +906 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.pxd +85 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx +534 -0
- sage/groups/perm_gps/partn_ref/data_structures.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/data_structures.pxd +576 -0
- sage/groups/perm_gps/partn_ref/data_structures.pyx +1792 -0
- sage/groups/perm_gps/partn_ref/double_coset.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/double_coset.pxd +45 -0
- sage/groups/perm_gps/partn_ref/double_coset.pyx +739 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.pxd +18 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.pyx +82 -0
- sage/groups/perm_gps/partn_ref/refinement_python.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_python.pxd +16 -0
- sage/groups/perm_gps/partn_ref/refinement_python.pyx +564 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.pxd +60 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.pyx +858 -0
- sage/interfaces/abc.py +140 -0
- sage/interfaces/all.py +58 -0
- sage/interfaces/all__sagemath_categories.py +1 -0
- sage/interfaces/expect.py +1643 -0
- sage/interfaces/interface.py +1682 -0
- sage/interfaces/process.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/interfaces/process.pxd +5 -0
- sage/interfaces/process.pyx +288 -0
- sage/interfaces/quit.py +167 -0
- sage/interfaces/sage0.py +604 -0
- sage/interfaces/sagespawn.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/interfaces/sagespawn.pyx +308 -0
- sage/interfaces/tab_completion.py +101 -0
- sage/misc/all__sagemath_categories.py +78 -0
- sage/misc/allocator.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/allocator.pxd +6 -0
- sage/misc/allocator.pyx +47 -0
- sage/misc/binary_tree.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/binary_tree.pxd +29 -0
- sage/misc/binary_tree.pyx +537 -0
- sage/misc/callable_dict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/callable_dict.pyx +89 -0
- sage/misc/citation.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/citation.pyx +159 -0
- sage/misc/converting_dict.py +293 -0
- sage/misc/defaults.py +129 -0
- sage/misc/derivative.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/derivative.pyx +223 -0
- sage/misc/functional.py +2005 -0
- sage/misc/html.py +589 -0
- sage/misc/latex.py +2673 -0
- sage/misc/latex_macros.py +236 -0
- sage/misc/latex_standalone.py +1833 -0
- sage/misc/map_threaded.py +38 -0
- sage/misc/mathml.py +76 -0
- sage/misc/method_decorator.py +88 -0
- sage/misc/mrange.py +755 -0
- sage/misc/multireplace.py +41 -0
- sage/misc/object_multiplexer.py +92 -0
- sage/misc/parser.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/parser.pyx +1107 -0
- sage/misc/random_testing.py +264 -0
- sage/misc/rest_index_of_methods.py +377 -0
- sage/misc/search.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/search.pxd +2 -0
- sage/misc/search.pyx +68 -0
- sage/misc/stopgap.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/stopgap.pyx +95 -0
- sage/misc/table.py +853 -0
- sage/monoids/all__sagemath_categories.py +1 -0
- sage/monoids/indexed_free_monoid.py +1071 -0
- sage/monoids/monoid.py +82 -0
- sage/numerical/all__sagemath_categories.py +1 -0
- sage/numerical/backends/all__sagemath_categories.py +1 -0
- sage/numerical/backends/generic_backend.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/generic_backend.pxd +61 -0
- sage/numerical/backends/generic_backend.pyx +1893 -0
- sage/numerical/backends/generic_sdp_backend.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/generic_sdp_backend.pxd +38 -0
- sage/numerical/backends/generic_sdp_backend.pyx +755 -0
- sage/parallel/all.py +6 -0
- sage/parallel/decorate.py +575 -0
- sage/parallel/map_reduce.py +1997 -0
- sage/parallel/multiprocessing_sage.py +76 -0
- sage/parallel/ncpus.py +35 -0
- sage/parallel/parallelism.py +364 -0
- sage/parallel/reference.py +47 -0
- sage/parallel/use_fork.py +333 -0
- sage/rings/abc.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/abc.pxd +31 -0
- sage/rings/abc.pyx +526 -0
- sage/rings/algebraic_closure_finite_field.py +1154 -0
- sage/rings/all__sagemath_categories.py +91 -0
- sage/rings/big_oh.py +227 -0
- sage/rings/continued_fraction.py +2754 -0
- sage/rings/continued_fraction_gosper.py +220 -0
- sage/rings/factorint.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/factorint.pyx +295 -0
- sage/rings/fast_arith.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/fast_arith.pxd +21 -0
- sage/rings/fast_arith.pyx +535 -0
- sage/rings/finite_rings/all__sagemath_categories.py +9 -0
- sage/rings/finite_rings/conway_polynomials.py +542 -0
- sage/rings/finite_rings/element_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/element_base.pxd +12 -0
- sage/rings/finite_rings/element_base.pyx +1176 -0
- sage/rings/finite_rings/finite_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/finite_field_base.pxd +7 -0
- sage/rings/finite_rings/finite_field_base.pyx +2171 -0
- sage/rings/finite_rings/finite_field_constructor.py +827 -0
- sage/rings/finite_rings/finite_field_prime_modn.py +372 -0
- sage/rings/finite_rings/galois_group.py +154 -0
- sage/rings/finite_rings/hom_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_finite_field.pxd +23 -0
- sage/rings/finite_rings/hom_finite_field.pyx +856 -0
- sage/rings/finite_rings/hom_prime_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_prime_finite_field.pxd +15 -0
- sage/rings/finite_rings/hom_prime_finite_field.pyx +164 -0
- sage/rings/finite_rings/homset.py +357 -0
- sage/rings/finite_rings/integer_mod.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/integer_mod.pxd +56 -0
- sage/rings/finite_rings/integer_mod.pyx +4586 -0
- sage/rings/finite_rings/integer_mod_limits.h +11 -0
- sage/rings/finite_rings/integer_mod_ring.py +2044 -0
- sage/rings/finite_rings/residue_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/residue_field.pxd +30 -0
- sage/rings/finite_rings/residue_field.pyx +1811 -0
- sage/rings/finite_rings/stdint.pxd +19 -0
- sage/rings/fraction_field.py +1452 -0
- sage/rings/fraction_field_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/fraction_field_element.pyx +1357 -0
- sage/rings/function_field/all.py +7 -0
- sage/rings/function_field/all__sagemath_categories.py +2 -0
- sage/rings/function_field/constructor.py +218 -0
- sage/rings/function_field/element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/element.pxd +11 -0
- sage/rings/function_field/element.pyx +1008 -0
- sage/rings/function_field/element_rational.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/element_rational.pyx +513 -0
- sage/rings/function_field/extensions.py +230 -0
- sage/rings/function_field/function_field.py +1468 -0
- sage/rings/function_field/function_field_rational.py +1005 -0
- sage/rings/function_field/ideal.py +1155 -0
- sage/rings/function_field/ideal_rational.py +629 -0
- sage/rings/function_field/jacobian_base.py +826 -0
- sage/rings/function_field/jacobian_hess.py +1053 -0
- sage/rings/function_field/jacobian_khuri_makdisi.py +1027 -0
- sage/rings/function_field/maps.py +1039 -0
- sage/rings/function_field/order.py +281 -0
- sage/rings/function_field/order_basis.py +586 -0
- sage/rings/function_field/order_rational.py +576 -0
- sage/rings/function_field/place.py +426 -0
- sage/rings/function_field/place_rational.py +181 -0
- sage/rings/generic.py +320 -0
- sage/rings/homset.py +332 -0
- sage/rings/ideal.py +1885 -0
- sage/rings/ideal_monoid.py +215 -0
- sage/rings/infinity.py +1890 -0
- sage/rings/integer.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/integer.pxd +45 -0
- sage/rings/integer.pyx +7874 -0
- sage/rings/integer_ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/integer_ring.pxd +8 -0
- sage/rings/integer_ring.pyx +1693 -0
- sage/rings/laurent_series_ring.py +931 -0
- sage/rings/laurent_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/laurent_series_ring_element.pxd +11 -0
- sage/rings/laurent_series_ring_element.pyx +1927 -0
- sage/rings/lazy_series.py +7815 -0
- sage/rings/lazy_series_ring.py +4356 -0
- sage/rings/localization.py +1043 -0
- sage/rings/morphism.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/morphism.pxd +39 -0
- sage/rings/morphism.pyx +3299 -0
- sage/rings/multi_power_series_ring.py +1145 -0
- sage/rings/multi_power_series_ring_element.py +2184 -0
- sage/rings/noncommutative_ideals.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/noncommutative_ideals.pyx +423 -0
- sage/rings/number_field/all__sagemath_categories.py +1 -0
- sage/rings/number_field/number_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/number_field/number_field_base.pxd +8 -0
- sage/rings/number_field/number_field_base.pyx +507 -0
- sage/rings/number_field/number_field_element_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/number_field/number_field_element_base.pxd +6 -0
- sage/rings/number_field/number_field_element_base.pyx +36 -0
- sage/rings/number_field/number_field_ideal.py +3550 -0
- sage/rings/padics/all__sagemath_categories.py +4 -0
- sage/rings/padics/local_generic.py +1670 -0
- sage/rings/padics/local_generic_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/padics/local_generic_element.pxd +5 -0
- sage/rings/padics/local_generic_element.pyx +1017 -0
- sage/rings/padics/misc.py +256 -0
- sage/rings/padics/padic_generic.py +1911 -0
- sage/rings/padics/pow_computer.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/padics/pow_computer.pxd +38 -0
- sage/rings/padics/pow_computer.pyx +671 -0
- sage/rings/padics/precision_error.py +24 -0
- sage/rings/polynomial/all__sagemath_categories.py +25 -0
- sage/rings/polynomial/commutative_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/commutative_polynomial.pxd +6 -0
- sage/rings/polynomial/commutative_polynomial.pyx +24 -0
- sage/rings/polynomial/cyclotomic.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/cyclotomic.pyx +404 -0
- sage/rings/polynomial/flatten.py +711 -0
- sage/rings/polynomial/ideal.py +102 -0
- sage/rings/polynomial/infinite_polynomial_element.py +1768 -0
- sage/rings/polynomial/infinite_polynomial_ring.py +1653 -0
- sage/rings/polynomial/laurent_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/laurent_polynomial.pxd +18 -0
- sage/rings/polynomial/laurent_polynomial.pyx +2190 -0
- sage/rings/polynomial/laurent_polynomial_ideal.py +590 -0
- sage/rings/polynomial/laurent_polynomial_ring.py +832 -0
- sage/rings/polynomial/laurent_polynomial_ring_base.py +708 -0
- sage/rings/polynomial/multi_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/multi_polynomial.pxd +12 -0
- sage/rings/polynomial/multi_polynomial.pyx +3082 -0
- sage/rings/polynomial/multi_polynomial_element.py +2570 -0
- sage/rings/polynomial/multi_polynomial_ideal.py +5771 -0
- sage/rings/polynomial/multi_polynomial_ring.py +947 -0
- sage/rings/polynomial/multi_polynomial_ring_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/multi_polynomial_ring_base.pxd +15 -0
- sage/rings/polynomial/multi_polynomial_ring_base.pyx +1855 -0
- sage/rings/polynomial/multi_polynomial_sequence.py +2204 -0
- sage/rings/polynomial/polydict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polydict.pxd +45 -0
- sage/rings/polynomial/polydict.pyx +2701 -0
- sage/rings/polynomial/polynomial_compiled.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_compiled.pxd +59 -0
- sage/rings/polynomial/polynomial_compiled.pyx +509 -0
- sage/rings/polynomial/polynomial_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_element.pxd +64 -0
- sage/rings/polynomial/polynomial_element.pyx +13255 -0
- sage/rings/polynomial/polynomial_element_generic.py +1637 -0
- sage/rings/polynomial/polynomial_fateman.py +97 -0
- sage/rings/polynomial/polynomial_quotient_ring.py +2465 -0
- sage/rings/polynomial/polynomial_quotient_ring_element.py +779 -0
- sage/rings/polynomial/polynomial_ring.py +3784 -0
- sage/rings/polynomial/polynomial_ring_constructor.py +1051 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.pxd +5 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.pyx +121 -0
- sage/rings/polynomial/polynomial_singular_interface.py +549 -0
- sage/rings/polynomial/symmetric_ideal.py +989 -0
- sage/rings/polynomial/symmetric_reduction.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/symmetric_reduction.pxd +8 -0
- sage/rings/polynomial/symmetric_reduction.pyx +669 -0
- sage/rings/polynomial/term_order.py +2279 -0
- sage/rings/polynomial/toy_buchberger.py +449 -0
- sage/rings/polynomial/toy_d_basis.py +387 -0
- sage/rings/polynomial/toy_variety.py +362 -0
- sage/rings/power_series_mpoly.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_mpoly.pxd +9 -0
- sage/rings/power_series_mpoly.pyx +161 -0
- sage/rings/power_series_poly.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_poly.pxd +10 -0
- sage/rings/power_series_poly.pyx +1317 -0
- sage/rings/power_series_ring.py +1441 -0
- sage/rings/power_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_ring_element.pxd +12 -0
- sage/rings/power_series_ring_element.pyx +3028 -0
- sage/rings/puiseux_series_ring.py +487 -0
- sage/rings/puiseux_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/puiseux_series_ring_element.pxd +7 -0
- sage/rings/puiseux_series_ring_element.pyx +1055 -0
- sage/rings/qqbar_decorators.py +167 -0
- sage/rings/quotient_ring.py +1598 -0
- sage/rings/quotient_ring_element.py +979 -0
- sage/rings/rational.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/rational.pxd +20 -0
- sage/rings/rational.pyx +4284 -0
- sage/rings/rational_field.py +1730 -0
- sage/rings/real_double.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/real_double.pxd +16 -0
- sage/rings/real_double.pyx +2218 -0
- sage/rings/real_lazy.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/real_lazy.pxd +30 -0
- sage/rings/real_lazy.pyx +1773 -0
- sage/rings/ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/ring.pxd +30 -0
- sage/rings/ring.pyx +850 -0
- sage/rings/semirings/all.py +3 -0
- sage/rings/semirings/non_negative_integer_semiring.py +107 -0
- sage/rings/semirings/tropical_mpolynomial.py +972 -0
- sage/rings/semirings/tropical_polynomial.py +997 -0
- sage/rings/semirings/tropical_semiring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/semirings/tropical_semiring.pyx +676 -0
- sage/rings/semirings/tropical_variety.py +1701 -0
- sage/rings/sum_of_squares.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/sum_of_squares.pxd +3 -0
- sage/rings/sum_of_squares.pyx +336 -0
- sage/rings/tests.py +504 -0
- sage/schemes/affine/affine_homset.py +508 -0
- sage/schemes/affine/affine_morphism.py +1574 -0
- sage/schemes/affine/affine_point.py +460 -0
- sage/schemes/affine/affine_rational_point.py +308 -0
- sage/schemes/affine/affine_space.py +1264 -0
- sage/schemes/affine/affine_subscheme.py +592 -0
- sage/schemes/affine/all.py +25 -0
- sage/schemes/all__sagemath_categories.py +5 -0
- sage/schemes/generic/algebraic_scheme.py +2092 -0
- sage/schemes/generic/all.py +5 -0
- sage/schemes/generic/ambient_space.py +400 -0
- sage/schemes/generic/divisor.py +465 -0
- sage/schemes/generic/divisor_group.py +313 -0
- sage/schemes/generic/glue.py +84 -0
- sage/schemes/generic/homset.py +820 -0
- sage/schemes/generic/hypersurface.py +234 -0
- sage/schemes/generic/morphism.py +2107 -0
- sage/schemes/generic/point.py +237 -0
- sage/schemes/generic/scheme.py +1190 -0
- sage/schemes/generic/spec.py +199 -0
- sage/schemes/product_projective/all.py +6 -0
- sage/schemes/product_projective/homset.py +236 -0
- sage/schemes/product_projective/morphism.py +517 -0
- sage/schemes/product_projective/point.py +568 -0
- sage/schemes/product_projective/rational_point.py +550 -0
- sage/schemes/product_projective/space.py +1301 -0
- sage/schemes/product_projective/subscheme.py +466 -0
- sage/schemes/projective/all.py +24 -0
- sage/schemes/projective/proj_bdd_height.py +453 -0
- sage/schemes/projective/projective_homset.py +718 -0
- sage/schemes/projective/projective_morphism.py +2792 -0
- sage/schemes/projective/projective_point.py +1484 -0
- sage/schemes/projective/projective_rational_point.py +569 -0
- sage/schemes/projective/projective_space.py +2571 -0
- sage/schemes/projective/projective_subscheme.py +1574 -0
- sage/sets/all.py +17 -0
- sage/sets/cartesian_product.py +376 -0
- sage/sets/condition_set.py +525 -0
- sage/sets/disjoint_set.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/disjoint_set.pxd +36 -0
- sage/sets/disjoint_set.pyx +998 -0
- sage/sets/disjoint_union_enumerated_sets.py +625 -0
- sage/sets/family.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/family.pxd +12 -0
- sage/sets/family.pyx +1556 -0
- sage/sets/finite_enumerated_set.py +406 -0
- sage/sets/finite_set_map_cy.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/finite_set_map_cy.pxd +34 -0
- sage/sets/finite_set_map_cy.pyx +708 -0
- sage/sets/finite_set_maps.py +591 -0
- sage/sets/image_set.py +448 -0
- sage/sets/integer_range.py +829 -0
- sage/sets/non_negative_integers.py +241 -0
- sage/sets/positive_integers.py +93 -0
- sage/sets/primes.py +188 -0
- sage/sets/real_set.py +2760 -0
- sage/sets/recursively_enumerated_set.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/recursively_enumerated_set.pxd +31 -0
- sage/sets/recursively_enumerated_set.pyx +2082 -0
- sage/sets/set.py +2083 -0
- sage/sets/set_from_iterator.py +1021 -0
- sage/sets/totally_ordered_finite_set.py +329 -0
- sage/symbolic/all__sagemath_categories.py +1 -0
- sage/symbolic/function.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/symbolic/function.pxd +29 -0
- sage/symbolic/function.pyx +1488 -0
- sage/symbolic/symbols.py +56 -0
- sage/tests/all__sagemath_categories.py +1 -0
- sage/tests/cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/tests/cython.pyx +37 -0
- sage/tests/stl_vector.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/tests/stl_vector.pyx +171 -0
- sage/typeset/all.py +6 -0
- sage/typeset/ascii_art.py +295 -0
- sage/typeset/character_art.py +789 -0
- sage/typeset/character_art_factory.py +572 -0
- sage/typeset/symbols.py +334 -0
- sage/typeset/unicode_art.py +183 -0
- sage/typeset/unicode_characters.py +101 -0
sage/databases/sql_db.py
ADDED
|
@@ -0,0 +1,2236 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-categories
|
|
2
|
+
r"""
|
|
3
|
+
Relational (sqlite) Databases Module
|
|
4
|
+
|
|
5
|
+
INFO:
|
|
6
|
+
|
|
7
|
+
This module implements classes (SQLDatabase and SQLQuery (pythonic
|
|
8
|
+
implementation for the user with little or no knowledge of sqlite))
|
|
9
|
+
that wrap the basic functionality of sqlite.
|
|
10
|
+
|
|
11
|
+
Databases are constructed via a triple indexed dictionary called a
|
|
12
|
+
skeleton. A skeleton should be constructed to fit the following format::
|
|
13
|
+
|
|
14
|
+
| - skeleton -- a triple-indexed dictionary
|
|
15
|
+
| - outer key -- table name
|
|
16
|
+
| - inner key -- column name
|
|
17
|
+
| - inner inner key -- one of the following:
|
|
18
|
+
| - ``primary_key`` -- boolean, whether column has been set as
|
|
19
|
+
primary key
|
|
20
|
+
| - ``index`` -- boolean, whether column has been set as index
|
|
21
|
+
| - ``unique`` -- boolean, whether column has been set as unique
|
|
22
|
+
| - ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``, ``'INTEGER'``,
|
|
23
|
+
``'REAL'``, or other user defined type
|
|
24
|
+
|
|
25
|
+
An example skeleton of a database with one table, that table with one
|
|
26
|
+
column::
|
|
27
|
+
|
|
28
|
+
{'table1':{'col1':{'primary_key':False, 'index':True, 'sql':'REAL'}}}
|
|
29
|
+
|
|
30
|
+
SQLDatabases can also be constructed via the add, drop, and commit
|
|
31
|
+
functions. The vacuum function is also useful for restoring hard disk space
|
|
32
|
+
after a database has shrunk in size.
|
|
33
|
+
|
|
34
|
+
A SQLQuery can be constructed by providing a query_dict, which is a
|
|
35
|
+
dictionary with the following sample format::
|
|
36
|
+
|
|
37
|
+
{'table_name': 'tblname', 'display_cols': ['col1', 'col2', 'col3'], 'expression':[col, operator, value]}
|
|
38
|
+
|
|
39
|
+
Finally a SQLQuery also allows the user to directly input the query
|
|
40
|
+
string for a database, and also supports the '?' syntax by allowing an
|
|
41
|
+
argument for a tuple of parameters to query.
|
|
42
|
+
|
|
43
|
+
For full details, please see the tutorial. sage.graphs.graph_database.py
|
|
44
|
+
is an example of implementing a database class in Sage using this
|
|
45
|
+
interface.
|
|
46
|
+
|
|
47
|
+
AUTHORS:
|
|
48
|
+
|
|
49
|
+
- R. Andrew Ohana (2011-07-16): refactored and rewrote most of the code;
|
|
50
|
+
merged the Generic classes into the non-Generic versions; changed the
|
|
51
|
+
skeleton format to include a boolean indicating whether the column stores
|
|
52
|
+
unique keys; changed the index names so as to avoid potential ambiguity
|
|
53
|
+
|
|
54
|
+
- Emily A. Kirkman (2008-09-20): added functionality to generate plots and
|
|
55
|
+
reformat output in show
|
|
56
|
+
|
|
57
|
+
- Emily A. Kirkman and Robert L. Miller (2007-06-17): initial version
|
|
58
|
+
"""
|
|
59
|
+
# FUTURE TODOs (Ignore for now):
|
|
60
|
+
# - order by clause in query strings
|
|
61
|
+
# - delete from query containing joins
|
|
62
|
+
# - add data by column
|
|
63
|
+
# - wrap sqlalchemy
|
|
64
|
+
# - create query interface (with interact)
|
|
65
|
+
# - allow kwds arguments to SQLQuery (like GraphQuery)
|
|
66
|
+
|
|
67
|
+
# ****************************************************************************
|
|
68
|
+
# Copyright (C) 2011 R. Andrew Ohana <andrew.ohana@gmail.com>
|
|
69
|
+
# Copyright (C) 2007 Emily A. Kirkman
|
|
70
|
+
# Robert L. Miller
|
|
71
|
+
#
|
|
72
|
+
# This program is free software: you can redistribute it and/or modify
|
|
73
|
+
# it under the terms of the GNU General Public License as published by
|
|
74
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
75
|
+
# (at your option) any later version.
|
|
76
|
+
# https://www.gnu.org/licenses/
|
|
77
|
+
# ****************************************************************************
|
|
78
|
+
|
|
79
|
+
import sqlite3 as sqlite
|
|
80
|
+
import os
|
|
81
|
+
import re
|
|
82
|
+
|
|
83
|
+
from sage.structure.sage_object import SageObject
|
|
84
|
+
|
|
85
|
+
sqlite_keywords = ['ABORT','ACTION','ADD','AFTER','ALL','ALTER','ANALYZE',
|
|
86
|
+
'AND','AS','ASC','ATTACH','AUTOINCREMENT','BEFORE','BEGIN','BETWEEN','BY',
|
|
87
|
+
'CASCADE','CASE','CAST','CHECK','COLLATE','COLUMN','COMMIT','CONFLICT',
|
|
88
|
+
'CONSTRAINT','CREATE','CROSS','CURRENT_DATE','CURRENT_TIME',
|
|
89
|
+
'CURRENT_TIMESTAMP','DATABASE','DEFAULT','DEFERRABLE','DEFERRED','DELETE',
|
|
90
|
+
'DESC','DETACH','DISTINCT','DROP','EACH','ELSE','END','ESCAPE','EXCEPT',
|
|
91
|
+
'EXCLUSIVE','EXISTS','EXPLAIN','FAIL','FOR','FOREIGN','FROM','FULL',
|
|
92
|
+
'GLOB','GROUP','HAVING','IF','IGNORE','IMMEDIATE','IN','INDEX','INDEXED',
|
|
93
|
+
'INITIALLY','INNER','INSERT','INSTEAD','INTERSECT','INTO','IS','ISNULL',
|
|
94
|
+
'JOIN','KEY','LEFT','LIKE','LIMIT','MATCH','NATURAL','NO','NOT','NOTNULL',
|
|
95
|
+
'NULL','OF','OFFSET','ON','OR','ORDER','OUTER','PLAN','PRAGMA','PRIMARY',
|
|
96
|
+
'QUERY','RAISE','REFERENCES','REGEXP','REINDEX','RELEASE','RENAME',
|
|
97
|
+
'REPLACE','RESTRICT','RIGHT','ROLLBACK','ROW','SAVEPOINT','SELECT','SET',
|
|
98
|
+
'TABLE','TEMP','TEMPORARY','THEN','TO','TRANSACTION','TRIGGER','UNION',
|
|
99
|
+
'UNIQUE','UPDATE','USING','VACUUM','VALUES','VIEW','VIRTUAL','WHEN',
|
|
100
|
+
'WHERE']
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def regexp(expr, item):
|
|
104
|
+
"""
|
|
105
|
+
Function to define regular expressions in pysqlite.
|
|
106
|
+
|
|
107
|
+
OUTPUT:
|
|
108
|
+
|
|
109
|
+
- ``True`` if parameter ``item`` matches the regular expression
|
|
110
|
+
parameter ``expr``
|
|
111
|
+
- ``False`` otherwise (i.e.: no match)
|
|
112
|
+
|
|
113
|
+
REFERENCES:
|
|
114
|
+
|
|
115
|
+
- [Ha2005]_
|
|
116
|
+
|
|
117
|
+
EXAMPLES::
|
|
118
|
+
|
|
119
|
+
sage: from sage.databases.sql_db import regexp
|
|
120
|
+
sage: regexp('.s.*','cs')
|
|
121
|
+
True
|
|
122
|
+
sage: regexp('.s.*','ccs')
|
|
123
|
+
False
|
|
124
|
+
sage: regexp('.s.*','cscccc')
|
|
125
|
+
True
|
|
126
|
+
"""
|
|
127
|
+
r = re.compile(expr)
|
|
128
|
+
return r.match(item) is not None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def verify_type(type):
|
|
132
|
+
"""
|
|
133
|
+
Verify that the specified ``type`` is one of the allowed strings.
|
|
134
|
+
|
|
135
|
+
EXAMPLES::
|
|
136
|
+
|
|
137
|
+
sage: from sage.databases.sql_db import verify_type
|
|
138
|
+
sage: verify_type('INT')
|
|
139
|
+
True
|
|
140
|
+
sage: verify_type('int')
|
|
141
|
+
True
|
|
142
|
+
sage: verify_type('float')
|
|
143
|
+
Traceback (most recent call last):
|
|
144
|
+
...
|
|
145
|
+
TypeError: float is not a legal type.
|
|
146
|
+
"""
|
|
147
|
+
types = ['INTEGER','INT','BOOLEAN','REAL','TEXT','BOOL','BLOB','NOTYPE']
|
|
148
|
+
if type.upper() not in types:
|
|
149
|
+
raise TypeError('%s is not a legal type.' % type)
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def verify_column(col_dict):
|
|
154
|
+
"""
|
|
155
|
+
Verify that ``col_dict`` is in proper format, and return a dict with
|
|
156
|
+
default values filled in. Proper format::
|
|
157
|
+
|
|
158
|
+
{'primary_key':False, 'index':False, 'unique': False, 'sql':'REAL'}
|
|
159
|
+
|
|
160
|
+
EXAMPLES::
|
|
161
|
+
|
|
162
|
+
sage: from sage.databases.sql_db import verify_column
|
|
163
|
+
sage: col = {'sql':'BOOLEAN'}
|
|
164
|
+
sage: verify_column(col)
|
|
165
|
+
{'index': False, 'primary_key': False, 'sql': 'BOOLEAN', 'unique': False}
|
|
166
|
+
sage: col = {'primary_key':True, 'sql':'INTEGER'}
|
|
167
|
+
sage: verify_column(col)
|
|
168
|
+
{'index': True, 'primary_key': True, 'sql': 'INTEGER', 'unique': True}
|
|
169
|
+
sage: verify_column({})
|
|
170
|
+
Traceback (most recent call last):
|
|
171
|
+
...
|
|
172
|
+
ValueError: SQL type must be declared, e.g. {'sql':'REAL'}.
|
|
173
|
+
"""
|
|
174
|
+
d = {}
|
|
175
|
+
d['primary_key'] = col_dict.get('primary_key', False)
|
|
176
|
+
d['index'] = col_dict.get('index', False) or d['primary_key']
|
|
177
|
+
d['unique'] = col_dict.get('unique', False) or d['primary_key']
|
|
178
|
+
if 'sql' not in col_dict:
|
|
179
|
+
raise ValueError("SQL type must be declared, e.g. {'sql':'REAL'}.")
|
|
180
|
+
if verify_type(col_dict['sql']):
|
|
181
|
+
d['sql'] = col_dict['sql']
|
|
182
|
+
return d
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def verify_operator(operator):
|
|
186
|
+
"""
|
|
187
|
+
Check that ``operator`` is one of the allowed strings.
|
|
188
|
+
Legal operators include the following strings:
|
|
189
|
+
|
|
190
|
+
- '='
|
|
191
|
+
- '<='
|
|
192
|
+
- '>='
|
|
193
|
+
- '<'
|
|
194
|
+
- '>'
|
|
195
|
+
- '<>'
|
|
196
|
+
- 'like'
|
|
197
|
+
- 'regexp'
|
|
198
|
+
- 'is null'
|
|
199
|
+
- 'is not null'
|
|
200
|
+
|
|
201
|
+
EXAMPLES::
|
|
202
|
+
|
|
203
|
+
sage: from sage.databases.sql_db import verify_operator
|
|
204
|
+
sage: verify_operator('<=')
|
|
205
|
+
True
|
|
206
|
+
sage: verify_operator('regexp')
|
|
207
|
+
True
|
|
208
|
+
sage: verify_operator('is null')
|
|
209
|
+
True
|
|
210
|
+
sage: verify_operator('not_an_operator')
|
|
211
|
+
Traceback (most recent call last):
|
|
212
|
+
...
|
|
213
|
+
TypeError: not_an_operator is not a legal operator.
|
|
214
|
+
"""
|
|
215
|
+
binaries = ['=','<=','>=','like','<','>','<>','regexp']
|
|
216
|
+
unaries = ['is null','is not null']
|
|
217
|
+
if operator not in binaries and operator not in unaries:
|
|
218
|
+
raise TypeError('%s is not a legal operator.' % operator)
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def construct_skeleton(database):
|
|
223
|
+
"""
|
|
224
|
+
Construct a database skeleton from the sql data. The skeleton data
|
|
225
|
+
structure is a triple indexed dictionary of the following format::
|
|
226
|
+
|
|
227
|
+
| - skeleton -- a triple-indexed dictionary
|
|
228
|
+
| - outer key -- table name
|
|
229
|
+
| - inner key -- column name
|
|
230
|
+
| - inner inner key -- one of the following:
|
|
231
|
+
| - ``primary_key`` -- boolean, whether column has been set as
|
|
232
|
+
primary key
|
|
233
|
+
| - ``index`` -- boolean, whether column has been set as index
|
|
234
|
+
| - ``unique`` -- boolean, whether column has been set as unique
|
|
235
|
+
| - ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``, ``'INTEGER'``,
|
|
236
|
+
``'REAL'``, or other user defined type
|
|
237
|
+
|
|
238
|
+
An example skeleton of a database with one table, that table with one
|
|
239
|
+
column::
|
|
240
|
+
|
|
241
|
+
{'table1':{'col1':{'primary_key':False, 'unique': True, 'index':True, 'sql':'REAL'}}}
|
|
242
|
+
|
|
243
|
+
EXAMPLES::
|
|
244
|
+
|
|
245
|
+
sage: # needs sage.graphs graph_database
|
|
246
|
+
sage: G = SQLDatabase(GraphDatabase().__dblocation__, False)
|
|
247
|
+
sage: from sage.databases.sql_db import construct_skeleton
|
|
248
|
+
sage: sorted(construct_skeleton(G))
|
|
249
|
+
['aut_grp', 'degrees', 'graph_data', 'misc', 'spectrum']
|
|
250
|
+
"""
|
|
251
|
+
skeleton = {}
|
|
252
|
+
cur = database.__connection__.cursor()
|
|
253
|
+
exe = cur.execute("SELECT name FROM sqlite_master WHERE TYPE='table'")
|
|
254
|
+
for table in exe.fetchall():
|
|
255
|
+
skeleton[table[0]] = {}
|
|
256
|
+
exe1 = cur.execute("PRAGMA table_info(%s)" % table[0])
|
|
257
|
+
for col in exe1.fetchall():
|
|
258
|
+
if not col[2]:
|
|
259
|
+
typ = 'NOTYPE'
|
|
260
|
+
else:
|
|
261
|
+
typ = col[2]
|
|
262
|
+
skeleton[table[0]][col[1]] = {'sql':typ,
|
|
263
|
+
'primary_key':(col[5] != 0), 'index':(col[5] != 0), 'unique': False}
|
|
264
|
+
exe2 = cur.execute("PRAGMA index_list(%s)" % table[0])
|
|
265
|
+
for col in exe2.fetchall():
|
|
266
|
+
if col[1].find('sqlite') == -1:
|
|
267
|
+
if os.path.basename(database.__dblocation__) == 'graphs.db':
|
|
268
|
+
name = col[1]
|
|
269
|
+
else:
|
|
270
|
+
name = col[1][len(table[0])+3:]
|
|
271
|
+
skeleton[table[0]][name]['index'] = True
|
|
272
|
+
skeleton[table[0]][name]['unique'] = bool(col[2])
|
|
273
|
+
else:
|
|
274
|
+
name = cur.execute("PRAGMA index_info(%s)" % col[1])
|
|
275
|
+
name = name.fetchone()[2]
|
|
276
|
+
skeleton[table[0]][name]['unique'] = bool(col[2])
|
|
277
|
+
return skeleton
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
p = 0
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _create_print_table(cur, col_titles, **kwds):
|
|
284
|
+
r"""
|
|
285
|
+
Create a nice printable table from the cursor given with the given
|
|
286
|
+
column titles.
|
|
287
|
+
|
|
288
|
+
KEYWORDS:
|
|
289
|
+
|
|
290
|
+
- ``max_field_size`` -- how wide each field can be
|
|
291
|
+
- ``format_cols`` -- dictionary that allows the user to specify the
|
|
292
|
+
format of a column's output by supplying a function. The format of
|
|
293
|
+
the dictionary is::
|
|
294
|
+
|
|
295
|
+
{'column_name':(lambda x: format_function(x))}
|
|
296
|
+
|
|
297
|
+
- ``plot_cols`` -- dictionary that allows the user to specify that a
|
|
298
|
+
plot should be drawn by the object generated by a data slice. Note
|
|
299
|
+
that plot kwds are permitted. The dictionary format is::
|
|
300
|
+
|
|
301
|
+
{'column_name':((lambda x: plot_function(x)), **kwds)}
|
|
302
|
+
|
|
303
|
+
- ``relabel_cols`` -- dictionary to specify a relabeling of column
|
|
304
|
+
headers. The dictionary format is::
|
|
305
|
+
|
|
306
|
+
{'table_name':{'old_col_name':'new_col_name'}}
|
|
307
|
+
|
|
308
|
+
- ``id_col`` -- reference to a column that can be used as an object
|
|
309
|
+
identifier for each row
|
|
310
|
+
|
|
311
|
+
- ``html_table`` -- boolean that if ``True`` creates an html table instead of
|
|
312
|
+
a print table. Always set to ``True`` in the notebook
|
|
313
|
+
|
|
314
|
+
EXAMPLES::
|
|
315
|
+
|
|
316
|
+
sage: DB = SQLDatabase()
|
|
317
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
|
|
318
|
+
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
|
|
319
|
+
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
|
|
320
|
+
sage: from sage.databases.sql_db import _create_print_table
|
|
321
|
+
sage: cur = r.__database__.__connection__.cursor()
|
|
322
|
+
sage: exe = cur.execute(r.__query_string__, r.__param_tuple__)
|
|
323
|
+
sage: _create_print_table(cur, [des[0] for des in cur.description])
|
|
324
|
+
'a1 \n--------------------\n0 \n1 \n1 '
|
|
325
|
+
"""
|
|
326
|
+
fcol_index = []
|
|
327
|
+
pcol_index = []
|
|
328
|
+
|
|
329
|
+
if 'format_cols' in kwds:
|
|
330
|
+
fcol_map = []
|
|
331
|
+
for col in kwds['format_cols']:
|
|
332
|
+
fcol_map.append(kwds['format_cols'][col])
|
|
333
|
+
fcol_index.append(col_titles.index(col))
|
|
334
|
+
if 'plot_cols' in kwds:
|
|
335
|
+
pcol_map = []
|
|
336
|
+
for col in kwds['plot_cols']:
|
|
337
|
+
pcol_map.append(kwds['plot_cols'][col])
|
|
338
|
+
pcol_index.append(col_titles.index(col))
|
|
339
|
+
|
|
340
|
+
max_field_size = kwds['max_field_size'] if 'max_field_size' in kwds \
|
|
341
|
+
else 20
|
|
342
|
+
id_col_index = col_titles.index(kwds['id_col']) if 'id_col' in kwds \
|
|
343
|
+
else None
|
|
344
|
+
|
|
345
|
+
if 'relabel_cols' in kwds:
|
|
346
|
+
relabel_cols = kwds['relabel_cols']
|
|
347
|
+
for i in range(len(col_titles)):
|
|
348
|
+
try:
|
|
349
|
+
col_titles[i] = relabel_cols[col_titles[i]]
|
|
350
|
+
except KeyError:
|
|
351
|
+
continue
|
|
352
|
+
global p
|
|
353
|
+
p = 0
|
|
354
|
+
|
|
355
|
+
def row_str(row, html):
|
|
356
|
+
f = 0
|
|
357
|
+
global p
|
|
358
|
+
cur_str = []
|
|
359
|
+
for index in range(len(col_titles)):
|
|
360
|
+
if index in pcol_index:
|
|
361
|
+
if html:
|
|
362
|
+
plot = pcol_map[p % len(pcol_map)](row[index])
|
|
363
|
+
plot.save('%d.png' % p, figsize=[1,1])
|
|
364
|
+
field_val = ' <td bgcolor=white align=center> ' \
|
|
365
|
+
+ '%s <br> <img src="cell://%d.png"> ' % (row[index],p) \
|
|
366
|
+
+ '</td>\n'
|
|
367
|
+
p += 1
|
|
368
|
+
else:
|
|
369
|
+
raise NotImplementedError('Cannot display plot on '
|
|
370
|
+
'command line.')
|
|
371
|
+
else:
|
|
372
|
+
if index in fcol_index:
|
|
373
|
+
if id_col_index is None:
|
|
374
|
+
field_val = fcol_map[f](row[index])
|
|
375
|
+
else:
|
|
376
|
+
field_val = fcol_map[f](row[index], row[id_col_index])
|
|
377
|
+
f += 1
|
|
378
|
+
else:
|
|
379
|
+
field_val = row[index]
|
|
380
|
+
if html:
|
|
381
|
+
field_val = ' <td bgcolor=white align=center> ' \
|
|
382
|
+
+ str(field_val) + ' </td>\n'
|
|
383
|
+
else:
|
|
384
|
+
field_val = str(field_val).ljust(max_field_size)
|
|
385
|
+
cur_str.append(field_val)
|
|
386
|
+
return ' '.join(cur_str)
|
|
387
|
+
|
|
388
|
+
if 'html_table' in kwds and kwds['html_table']:
|
|
389
|
+
# Notebook Version
|
|
390
|
+
ret = '<html><!--notruncate-->\n'
|
|
391
|
+
ret += ' <table bgcolor=lightgrey cellpadding=0>\n'
|
|
392
|
+
ret += ' <tr>\n <td bgcolor=white align=center> '
|
|
393
|
+
ret += (' </td>\n <td bgcolor=white '
|
|
394
|
+
+ 'align=center> ').join(col_titles)
|
|
395
|
+
ret += ' </td>\n </tr>\n'
|
|
396
|
+
ret += '\n'.join([' <tr>\n ' + row_str(row, True) + ' </tr>'
|
|
397
|
+
for row in cur])
|
|
398
|
+
ret += '\n </table>\n</html>'
|
|
399
|
+
else:
|
|
400
|
+
# Command Prompt Version
|
|
401
|
+
ret = ' '.join([col.ljust(max_field_size) for col in col_titles])
|
|
402
|
+
ret += '\n' + '-' * max_field_size * len(col_titles) + '\n'
|
|
403
|
+
ret += '\n'.join([row_str(row, False) for row in cur])
|
|
404
|
+
return ret
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class SQLQuery(SageObject):
|
|
408
|
+
def __init__(self, database, *args, **kwds):
|
|
409
|
+
"""
|
|
410
|
+
A query for a SQLite database.
|
|
411
|
+
|
|
412
|
+
INPUT:
|
|
413
|
+
|
|
414
|
+
- ``database`` -- a SQLDatabase object
|
|
415
|
+
- ``query_dict`` -- dictionary specifying the query itself. The
|
|
416
|
+
format is::
|
|
417
|
+
|
|
418
|
+
{'table_name':'tblname', 'display_cols':['col1', 'col2','col3'], 'expression': [col, operator, value]}
|
|
419
|
+
|
|
420
|
+
NOTE:
|
|
421
|
+
Every SQL type we are using is ultimately represented as a string,
|
|
422
|
+
so if you wish to save actual strings to a database, you actually
|
|
423
|
+
need to do something like: '"value"'.
|
|
424
|
+
|
|
425
|
+
See the documentation of ``SQLDatabase`` for an introduction to using
|
|
426
|
+
SQLite in Sage.
|
|
427
|
+
|
|
428
|
+
EXAMPLES::
|
|
429
|
+
|
|
430
|
+
sage: D = SQLDatabase()
|
|
431
|
+
sage: D.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
|
|
432
|
+
sage: D.add_data('simon',[(0,0),(1,2),(2,4)])
|
|
433
|
+
sage: r = SQLQuery(D, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 3]})
|
|
434
|
+
sage: r.show()
|
|
435
|
+
a1
|
|
436
|
+
--------------------
|
|
437
|
+
0
|
|
438
|
+
1
|
|
439
|
+
|
|
440
|
+
Test that :issue:`27562` is fixed::
|
|
441
|
+
|
|
442
|
+
sage: D = SQLDatabase()
|
|
443
|
+
sage: r = SQLQuery(D, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 3]})
|
|
444
|
+
Traceback (most recent call last):
|
|
445
|
+
...
|
|
446
|
+
ValueError: Database has no table simon
|
|
447
|
+
sage: D.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
|
|
448
|
+
sage: D.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
|
|
449
|
+
Traceback (most recent call last):
|
|
450
|
+
...
|
|
451
|
+
ValueError: Database already has a table named simon
|
|
452
|
+
sage: SQLQuery(D, {'table_name':'simon', 'display_cols':['a1'], 'expression':['c1','>',2]})
|
|
453
|
+
Traceback (most recent call last):
|
|
454
|
+
...
|
|
455
|
+
ValueError: Table has no column c1
|
|
456
|
+
"""
|
|
457
|
+
if not isinstance(database, SQLDatabase):
|
|
458
|
+
raise TypeError('%s is not a valid SQLDatabase' % database)
|
|
459
|
+
self.__database__ = database
|
|
460
|
+
total_args = len(args) + len(kwds)
|
|
461
|
+
if total_args == 0:
|
|
462
|
+
self.__query_string__ = ''
|
|
463
|
+
self.__param_tuple__ = tuple()
|
|
464
|
+
self.__query_dict__ = {}
|
|
465
|
+
return
|
|
466
|
+
for x in args:
|
|
467
|
+
if isinstance(x,dict):
|
|
468
|
+
if 'query_dict' not in kwds:
|
|
469
|
+
kwds['query_dict'] = x
|
|
470
|
+
elif isinstance(x, str):
|
|
471
|
+
if 'query_string' not in kwds:
|
|
472
|
+
kwds['query_string'] = x
|
|
473
|
+
elif isinstance(x, tuple):
|
|
474
|
+
if 'param_tuple' not in kwds:
|
|
475
|
+
kwds['param_tuple'] = x
|
|
476
|
+
if total_args > 2 or not ('query_dict' in kwds or
|
|
477
|
+
'query_string' in kwds) or ('query_dict' in kwds and
|
|
478
|
+
('param_tuple' in kwds or 'query_string' in kwds)):
|
|
479
|
+
raise ValueError('Query must be constructed with either a '
|
|
480
|
+
+ 'dictionary or a string and tuple')
|
|
481
|
+
|
|
482
|
+
if 'query_dict' in kwds:
|
|
483
|
+
query_dict = kwds['query_dict']
|
|
484
|
+
else:
|
|
485
|
+
self.__query_string__ = kwds['query_string']
|
|
486
|
+
if 'param_tuple' in kwds:
|
|
487
|
+
self.__param_tuple__ = tuple(str(x) for x in kwds['param_tuple'])
|
|
488
|
+
else:
|
|
489
|
+
self.__param_tuple__ = tuple()
|
|
490
|
+
return
|
|
491
|
+
if query_dict:
|
|
492
|
+
skel = database.__skeleton__
|
|
493
|
+
if query_dict['table_name'] not in skel:
|
|
494
|
+
raise ValueError("Database has no table %s"
|
|
495
|
+
% query_dict['table_name'])
|
|
496
|
+
table_name = query_dict['table_name']
|
|
497
|
+
if query_dict['display_cols'] is not None:
|
|
498
|
+
for column in query_dict['display_cols']:
|
|
499
|
+
if column not in skel[table_name]:
|
|
500
|
+
raise ValueError("Table has no column %s" % column)
|
|
501
|
+
if query_dict['expression'][0] not in skel[table_name]:
|
|
502
|
+
raise ValueError("Table has no column %s"
|
|
503
|
+
% query_dict['expression'][0])
|
|
504
|
+
|
|
505
|
+
self.__query_dict__ = query_dict
|
|
506
|
+
self.__param_tuple__ = (str(query_dict['expression'][2]),)
|
|
507
|
+
verify_operator(query_dict['expression'][1])
|
|
508
|
+
if query_dict['display_cols'] is None:
|
|
509
|
+
self.__query_string__ = 'SELECT , FROM %s WHERE ' % table_name \
|
|
510
|
+
+ '%s.%s ' % (table_name, query_dict['expression'][0]) \
|
|
511
|
+
+ '%s ?' % query_dict['expression'][1]
|
|
512
|
+
else:
|
|
513
|
+
query_dict['display_cols'] = ['%s.%s' % (table_name, x)
|
|
514
|
+
for x in query_dict['display_cols']]
|
|
515
|
+
self.__query_string__ = 'SELECT ' \
|
|
516
|
+
+ ', '.join(query_dict['display_cols']) + ' FROM ' \
|
|
517
|
+
+ '%s WHERE %s.' % (table_name, table_name) \
|
|
518
|
+
+ '%s ' % query_dict['expression'][0] \
|
|
519
|
+
+ '%s ?' % query_dict['expression'][1]
|
|
520
|
+
else:
|
|
521
|
+
self.__query_dict__ = {}
|
|
522
|
+
self.__param_tuple__ = tuple()
|
|
523
|
+
self.__query_string__ = ''
|
|
524
|
+
|
|
525
|
+
def __repr__(self):
|
|
526
|
+
"""
|
|
527
|
+
Override the print output to display useful info regarding the
|
|
528
|
+
query.
|
|
529
|
+
|
|
530
|
+
EXAMPLES::
|
|
531
|
+
|
|
532
|
+
sage: # needs sage.graphs graph_database
|
|
533
|
+
sage: G = GraphDatabase()
|
|
534
|
+
sage: q = 'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data WHERE graph_id<=(?) AND num_vertices=(?)'
|
|
535
|
+
sage: param = (22,5)
|
|
536
|
+
sage: SQLQuery(G,q,param)
|
|
537
|
+
Query for sql database: ...graphs.db
|
|
538
|
+
Query string: SELECT graph_id,graph6,num_vertices,num_edges FROM
|
|
539
|
+
graph_data WHERE graph_id<=(?) AND num_vertices=(?)
|
|
540
|
+
Parameter tuple: ('22', '5')
|
|
541
|
+
sage: r = 'SELECT graph6 FROM graph_data WHERE num_vertices<=3'
|
|
542
|
+
sage: SQLQuery(G,r)
|
|
543
|
+
Query for sql database: ...graphs.db
|
|
544
|
+
Query string: SELECT graph6 FROM graph_data WHERE num_vertices<=3
|
|
545
|
+
"""
|
|
546
|
+
if not self.__query_string__:
|
|
547
|
+
return 'Empty query on %s.' % self.__database__.__dblocation__
|
|
548
|
+
return "Query for sql database: %s" % self.__database__.__dblocation__ \
|
|
549
|
+
+ "\nQuery string: %s" % self.__query_string__ \
|
|
550
|
+
+ ("\nParameter tuple: %s" % str(self.__param_tuple__) if
|
|
551
|
+
self.__param_tuple__ else "")
|
|
552
|
+
|
|
553
|
+
def get_query_string(self):
|
|
554
|
+
"""
|
|
555
|
+
Return a copy of the query string.
|
|
556
|
+
|
|
557
|
+
EXAMPLES::
|
|
558
|
+
|
|
559
|
+
sage: # needs sage.graphs graph_database
|
|
560
|
+
sage: G = GraphDatabase()
|
|
561
|
+
sage: q = 'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data WHERE graph_id<=(?) AND num_vertices=(?)'
|
|
562
|
+
sage: param = (22,5)
|
|
563
|
+
sage: SQLQuery(G,q,param).get_query_string()
|
|
564
|
+
'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data
|
|
565
|
+
WHERE graph_id<=(?) AND num_vertices=(?)'
|
|
566
|
+
sage: r = 'SELECT graph6 FROM graph_data WHERE num_vertices<=3'
|
|
567
|
+
sage: SQLQuery(G,r).get_query_string()
|
|
568
|
+
'SELECT graph6 FROM graph_data WHERE num_vertices<=3'
|
|
569
|
+
"""
|
|
570
|
+
from copy import copy
|
|
571
|
+
return copy(self.__query_string__)
|
|
572
|
+
|
|
573
|
+
def __iter__(self):
|
|
574
|
+
"""
|
|
575
|
+
Return an iterator over the results of the query.
|
|
576
|
+
|
|
577
|
+
EXAMPLES::
|
|
578
|
+
|
|
579
|
+
sage: # needs sage.graphs graph_database
|
|
580
|
+
sage: G = GraphDatabase()
|
|
581
|
+
sage: q = 'SELECT graph_id,graph6 FROM graph_data WHERE num_vertices=(?)'
|
|
582
|
+
sage: param = (5,)
|
|
583
|
+
sage: Q = SQLQuery(G,q,param)
|
|
584
|
+
sage: it = Q.__iter__()
|
|
585
|
+
sage: next(it)
|
|
586
|
+
(18, 'D??')
|
|
587
|
+
sage: next(it)
|
|
588
|
+
(19, 'D?C')
|
|
589
|
+
sage: skip = [next(it) for _ in range(15)]
|
|
590
|
+
sage: next(it)
|
|
591
|
+
(35, 'DBk')
|
|
592
|
+
"""
|
|
593
|
+
try:
|
|
594
|
+
cur = self.__database__.__connection__.cursor()
|
|
595
|
+
return cur.execute(self.__query_string__, self.__param_tuple__)
|
|
596
|
+
except sqlite.OperationalError:
|
|
597
|
+
raise RuntimeError('Failure to fetch query.')
|
|
598
|
+
|
|
599
|
+
def query_results(self):
|
|
600
|
+
"""
|
|
601
|
+
Run the query by executing the ``__query_string__``. Return the
|
|
602
|
+
results of the query in a list.
|
|
603
|
+
|
|
604
|
+
EXAMPLES::
|
|
605
|
+
|
|
606
|
+
sage: # needs sage.graphs graph_database
|
|
607
|
+
sage: G = GraphDatabase()
|
|
608
|
+
sage: q = 'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data WHERE graph_id<=(?) AND num_vertices=(?)'
|
|
609
|
+
sage: param = (22,5)
|
|
610
|
+
sage: Q = SQLQuery(G,q,param)
|
|
611
|
+
sage: Q.query_results()
|
|
612
|
+
[(18, 'D??', 5, 0), (19, 'D?C', 5, 1), (20, 'D?K', 5, 2),
|
|
613
|
+
(21, 'D@O', 5, 2), (22, 'D?[', 5, 3)]
|
|
614
|
+
sage: R = SQLQuery(G,{'table_name':'graph_data', 'display_cols':['graph6'], 'expression':['num_vertices','=',4]})
|
|
615
|
+
sage: R.query_results()
|
|
616
|
+
[('C?',), ('C@',), ('CB',), ('CK',), ('CF',), ('CJ',),
|
|
617
|
+
('CL',), ('CN',), ('C]',), ('C^',), ('C~',)]
|
|
618
|
+
"""
|
|
619
|
+
return list(self)
|
|
620
|
+
|
|
621
|
+
def show(self, **kwds):
|
|
622
|
+
"""
|
|
623
|
+
Display the result of the query in table format.
|
|
624
|
+
|
|
625
|
+
KEYWORDS:
|
|
626
|
+
|
|
627
|
+
- ``max_field_size`` -- how wide each field can be
|
|
628
|
+
- ``format_cols`` -- dictionary that allows the user to specify the
|
|
629
|
+
format of a column's output by supplying a function. The format of
|
|
630
|
+
the dictionary is::
|
|
631
|
+
|
|
632
|
+
{'column_name':(lambda x: format_function(x))}
|
|
633
|
+
|
|
634
|
+
- ``plot_cols`` -- dictionary that allows the user to specify that a
|
|
635
|
+
plot should be drawn by the object generated by a data slice. Note
|
|
636
|
+
that plot kwds are permitted. The dictionary format is::
|
|
637
|
+
|
|
638
|
+
{'column_name':((lambda x: plot_function(x)), **kwds)}
|
|
639
|
+
|
|
640
|
+
- ``relabel_cols`` -- dictionary to specify a relabeling of column
|
|
641
|
+
headers. The dictionary format is::
|
|
642
|
+
|
|
643
|
+
{'table_name':{'old_col_name':'new_col_name'}}
|
|
644
|
+
|
|
645
|
+
- ``id_col`` -- reference to a column that can be used as an object
|
|
646
|
+
identifier for each row
|
|
647
|
+
|
|
648
|
+
EXAMPLES::
|
|
649
|
+
|
|
650
|
+
sage: DB = SQLDatabase()
|
|
651
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
|
|
652
|
+
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
|
|
653
|
+
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
|
|
654
|
+
sage: r.show()
|
|
655
|
+
a1
|
|
656
|
+
--------------------
|
|
657
|
+
0
|
|
658
|
+
1
|
|
659
|
+
1
|
|
660
|
+
|
|
661
|
+
sage: # needs sage.graphs graph_database
|
|
662
|
+
sage: D = GraphDatabase()
|
|
663
|
+
sage: from sage.graphs.graph_database import valid_kwds, data_to_degseq
|
|
664
|
+
sage: relabel = {}
|
|
665
|
+
sage: for col in valid_kwds:
|
|
666
|
+
....: relabel[col] = ' '.join([word.capitalize() for word in col.split('_')])
|
|
667
|
+
sage: q = GraphQuery(display_cols=['graph6','degree_sequence'], num_vertices=4)
|
|
668
|
+
sage: SQLQuery.show(q, format_cols={'degree_sequence':(lambda x,y: data_to_degseq(x,y))}, relabel_cols=relabel, id_col='graph6')
|
|
669
|
+
Graph6 Degree Sequence
|
|
670
|
+
----------------------------------------
|
|
671
|
+
C? [0, 0, 0, 0]
|
|
672
|
+
C@ [0, 0, 1, 1]
|
|
673
|
+
CB [0, 1, 1, 2]
|
|
674
|
+
CF [1, 1, 1, 3]
|
|
675
|
+
CJ [0, 2, 2, 2]
|
|
676
|
+
CK [1, 1, 1, 1]
|
|
677
|
+
CL [1, 1, 2, 2]
|
|
678
|
+
CN [1, 2, 2, 3]
|
|
679
|
+
C] [2, 2, 2, 2]
|
|
680
|
+
C^ [2, 2, 3, 3]
|
|
681
|
+
C~ [3, 3, 3, 3]
|
|
682
|
+
"""
|
|
683
|
+
if not self.__query_string__:
|
|
684
|
+
return self.__database__.show()
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
cur = self.__database__.__connection__.cursor()
|
|
688
|
+
cur.execute(self.__query_string__, self.__param_tuple__)
|
|
689
|
+
except Exception:
|
|
690
|
+
raise RuntimeError('Failure to fetch query.')
|
|
691
|
+
|
|
692
|
+
print(_create_print_table(cur, [des[0] for des in cur.description],
|
|
693
|
+
**kwds))
|
|
694
|
+
|
|
695
|
+
def __copy__(self):
|
|
696
|
+
"""
|
|
697
|
+
Return a copy of itself.
|
|
698
|
+
|
|
699
|
+
EXAMPLES::
|
|
700
|
+
|
|
701
|
+
sage: # needs sage.graphs graph_database
|
|
702
|
+
sage: G = GraphDatabase()
|
|
703
|
+
sage: Q = SQLQuery(G, {'table_name':'graph_data', 'display_cols':['graph_id','graph6','num_vertices'], 'expression':['num_edges','<',3]})
|
|
704
|
+
sage: R = copy(Q)
|
|
705
|
+
sage: R.__query_string__ = ''
|
|
706
|
+
sage: Q.__query_string__ == ''
|
|
707
|
+
False
|
|
708
|
+
"""
|
|
709
|
+
d = SQLQuery(self.__database__)
|
|
710
|
+
d.__query_dict__ = self.__query_dict__
|
|
711
|
+
d.__query_string__ = self.__query_string__
|
|
712
|
+
d.__param_tuple__ = self.__param_tuple__
|
|
713
|
+
return d
|
|
714
|
+
|
|
715
|
+
def intersect(self, other, join_table=None, join_dict=None,
|
|
716
|
+
in_place=False):
|
|
717
|
+
"""
|
|
718
|
+
Return a new ``SQLQuery`` that is the intersection of ``self`` and
|
|
719
|
+
``other``. ``join_table`` and ``join_dict`` can be ``None`` iff the
|
|
720
|
+
two queries only search one table in the database. All display columns
|
|
721
|
+
will be concatenated in order: ``self`` display cols + other display cols.
|
|
722
|
+
|
|
723
|
+
INPUT:
|
|
724
|
+
|
|
725
|
+
- ``other`` -- the ``SQLQuery`` to intersect with
|
|
726
|
+
- ``join_table`` -- base table to join on (This table should have at
|
|
727
|
+
least one column in each table to join on).
|
|
728
|
+
- ``join_dict`` -- dictionary that represents the join structure for
|
|
729
|
+
the new query. (Must include a mapping for all tables, including
|
|
730
|
+
those previously joined in either query). Structure is given by::
|
|
731
|
+
|
|
732
|
+
{'join_table1':('corr_base_col1', 'col1'), 'join_table2':('corr_base_col2', 'col2')}
|
|
733
|
+
|
|
734
|
+
where ``join_table1`` is to be joined with ``join_table`` on
|
|
735
|
+
``join_table.corr_base_col1 = join_table1.col1``
|
|
736
|
+
|
|
737
|
+
EXAMPLES::
|
|
738
|
+
|
|
739
|
+
sage: DB = SQLDatabase()
|
|
740
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
741
|
+
sage: DB.create_table('lucy',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
742
|
+
sage: DB.add_data('simon', [(0,5),(1,4)])
|
|
743
|
+
sage: DB.add_data('lucy', [(1,1),(1,4)])
|
|
744
|
+
sage: q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['b2'], 'expression':['a1','=',1]})
|
|
745
|
+
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
|
|
746
|
+
sage: s = q.intersect(r, 'simon', {'lucy':('a1','a1')})
|
|
747
|
+
sage: s.get_query_string()
|
|
748
|
+
'SELECT lucy.b2,simon.a1 FROM simon INNER JOIN lucy ON
|
|
749
|
+
simon.a1=lucy.a1 WHERE ( lucy.a1 = ? ) AND ( simon.b2 <= ? )'
|
|
750
|
+
sage: s.query_results()
|
|
751
|
+
[(1, 1), (4, 1)]
|
|
752
|
+
sage: s = q.intersect(r)
|
|
753
|
+
Traceback (most recent call last):
|
|
754
|
+
...
|
|
755
|
+
ValueError: Input queries query different tables but join
|
|
756
|
+
parameters are NoneType
|
|
757
|
+
sage: s.__query_string__ == q.__query_string__
|
|
758
|
+
False
|
|
759
|
+
sage: q.intersect(r, 'simon', {'lucy':('a1','a1')}, True)
|
|
760
|
+
sage: q.__query_string__ == s.__query_string__
|
|
761
|
+
True
|
|
762
|
+
"""
|
|
763
|
+
if self.__query_dict__ is None or other.__query_dict__ is None:
|
|
764
|
+
raise RuntimeError('Queries must be constructed using a '
|
|
765
|
+
+ 'dictionary in order to be intersected.')
|
|
766
|
+
if self.__database__ != other.__database__:
|
|
767
|
+
raise TypeError('Queries %s and %s must be ' % (self, other)
|
|
768
|
+
+ 'attached to the same database.')
|
|
769
|
+
|
|
770
|
+
if in_place:
|
|
771
|
+
if not self.__query_string__:
|
|
772
|
+
self.__query_string__ = other.__query_string__
|
|
773
|
+
self.__param_tuple__ = other.__param_tuple__
|
|
774
|
+
elif not other.__query_string__:
|
|
775
|
+
return
|
|
776
|
+
else:
|
|
777
|
+
self._merge_queries(other, self, join_table, join_dict, 'AND')
|
|
778
|
+
else:
|
|
779
|
+
from copy import copy
|
|
780
|
+
if not self.__query_string__:
|
|
781
|
+
return copy(other)
|
|
782
|
+
if not other.__query_string__:
|
|
783
|
+
return copy(self)
|
|
784
|
+
return self._merge_queries(other, copy(self), join_table,
|
|
785
|
+
join_dict, 'AND')
|
|
786
|
+
|
|
787
|
+
def _merge_queries(self, other, ret, join_table, join_dict, operator):
|
|
788
|
+
"""
|
|
789
|
+
The query ``ret`` is set to a new ``SQLQuery`` that is combines self
|
|
790
|
+
and other through ``operator``. The other arguments are the same as
|
|
791
|
+
``intersect`` and ``union``.
|
|
792
|
+
|
|
793
|
+
EXAMPLES::
|
|
794
|
+
|
|
795
|
+
sage: DB = SQLDatabase()
|
|
796
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
797
|
+
sage: DB.create_table('lucy',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
798
|
+
sage: DB.add_data('simon', [(0,5),(1,4)])
|
|
799
|
+
sage: DB.add_data('lucy', [(1,1),(1,4)])
|
|
800
|
+
sage: q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['b2'], 'expression':['a1','=',1]})
|
|
801
|
+
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
|
|
802
|
+
sage: s = q._merge_queries(r, copy(q), 'simon', {'lucy':('a1','a1')}, 'OR')
|
|
803
|
+
sage: s.get_query_string()
|
|
804
|
+
'SELECT lucy.b2,simon.a1 FROM simon INNER JOIN lucy ON
|
|
805
|
+
simon.a1=lucy.a1 WHERE ( lucy.a1 = ? ) OR ( simon.b2 <= ? )'
|
|
806
|
+
sage: s.query_results()
|
|
807
|
+
[(1, 1), (4, 1)]
|
|
808
|
+
"""
|
|
809
|
+
if join_table is None or join_dict is None:
|
|
810
|
+
pattern = ' JOIN '
|
|
811
|
+
if re.search(pattern, self.__query_string__) \
|
|
812
|
+
or re.search(pattern, other.__query_string__):
|
|
813
|
+
raise TypeError('Input queries have joins but join '
|
|
814
|
+
+ 'parameters are NoneType')
|
|
815
|
+
s = ((self.__query_string__).upper()).split('FROM ')
|
|
816
|
+
o = ((other.__query_string__).upper()).split('FROM ')
|
|
817
|
+
s = s[1].split(' WHERE ')
|
|
818
|
+
o = o[1].split(' WHERE ')
|
|
819
|
+
if s[0] != o[0]:
|
|
820
|
+
raise ValueError('Input queries query different tables but '
|
|
821
|
+
+ 'join parameters are NoneType')
|
|
822
|
+
|
|
823
|
+
# inner join clause
|
|
824
|
+
if join_dict is not None:
|
|
825
|
+
joins = join_table
|
|
826
|
+
for table in join_dict:
|
|
827
|
+
joins += ' INNER JOIN %s ON %s.' % (table, join_table) \
|
|
828
|
+
+ '%s=%s.' % (join_dict[table][0], table) \
|
|
829
|
+
+ '%s ' % join_dict[table][1]
|
|
830
|
+
ret.__query_string__ = re.sub(' FROM .* WHERE ', ' FROM ' + joins
|
|
831
|
+
+ 'WHERE ', self.__query_string__)
|
|
832
|
+
|
|
833
|
+
# concatenate display cols
|
|
834
|
+
disp1 = ret.__query_string__.split(' FROM')
|
|
835
|
+
disp2 = other.__query_string__.split(' FROM')
|
|
836
|
+
disp1.insert(1, ',%s FROM' % disp2[0].split('SELECT ')[1])
|
|
837
|
+
new_query = ''.join(disp1)
|
|
838
|
+
|
|
839
|
+
# concatenate where clause
|
|
840
|
+
new_query = re.sub(' WHERE ', ' WHERE ( ',
|
|
841
|
+
new_query)
|
|
842
|
+
new_query += re.sub('^.* WHERE ', f' ) {operator} ( ',
|
|
843
|
+
other.__query_string__)
|
|
844
|
+
ret.__query_string__ = new_query + ' )'
|
|
845
|
+
|
|
846
|
+
ret.__param_tuple__ = self.__param_tuple__ + other.__param_tuple__
|
|
847
|
+
|
|
848
|
+
return ret
|
|
849
|
+
|
|
850
|
+
def union(self, other, join_table=None, join_dict=None, in_place=False):
|
|
851
|
+
"""
|
|
852
|
+
Return a new ``SQLQuery`` that is the union of ``self`` and ``other``.
|
|
853
|
+
``join_table`` and ``join_dict`` can be ``None`` iff the two queries
|
|
854
|
+
only search one table in the database. All display columns will be
|
|
855
|
+
concatenated in order: ``self`` display cols + other display cols.
|
|
856
|
+
|
|
857
|
+
INPUT:
|
|
858
|
+
|
|
859
|
+
- ``other`` -- the ``SQLQuery`` to union with
|
|
860
|
+
- ``join_table`` -- base table to join on (This table should have at
|
|
861
|
+
least one column in each table to join on).
|
|
862
|
+
- ``join_dict`` -- dictionary that represents the join structure for
|
|
863
|
+
the new query. (Must include a mapping for all tables, including
|
|
864
|
+
those previously joined in either query). Structure is given by::
|
|
865
|
+
|
|
866
|
+
{'join_table1':('corr_base_col1', 'col1'), 'join_table2':('corr_base_col2', 'col2')}
|
|
867
|
+
|
|
868
|
+
where ``join_table1`` is to be joined with ``join_table`` on
|
|
869
|
+
``join_table.corr_base_col1=join_table1.col1``
|
|
870
|
+
|
|
871
|
+
EXAMPLES::
|
|
872
|
+
|
|
873
|
+
sage: DB = SQLDatabase()
|
|
874
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
875
|
+
sage: DB.create_table('lucy',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
876
|
+
sage: DB.add_data('simon', [(0,5),(1,4)])
|
|
877
|
+
sage: DB.add_data('lucy', [(1,1),(1,4)])
|
|
878
|
+
sage: q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['b2'], 'expression':['a1','=',1]})
|
|
879
|
+
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
|
|
880
|
+
sage: s = q.union(r, 'simon', {'lucy':('a1','a1')})
|
|
881
|
+
sage: s.get_query_string()
|
|
882
|
+
'SELECT lucy.b2,simon.a1 FROM simon INNER JOIN lucy ON
|
|
883
|
+
simon.a1=lucy.a1 WHERE ( lucy.a1 = ? ) OR ( simon.b2 <= ? )'
|
|
884
|
+
sage: s.query_results()
|
|
885
|
+
[(1, 1), (4, 1)]
|
|
886
|
+
"""
|
|
887
|
+
if self.__query_dict__ is None or other.__query_dict__ is None:
|
|
888
|
+
raise RuntimeError('Queries must be constructed using a '
|
|
889
|
+
+ 'dictionary in order to be unioned.')
|
|
890
|
+
if self.__database__ != other.__database__:
|
|
891
|
+
raise TypeError('Queries %s and %s must be ' % (self, other)
|
|
892
|
+
+ 'attached to the same database.')
|
|
893
|
+
|
|
894
|
+
if in_place:
|
|
895
|
+
if self.__query_string__ and other.__query_string__:
|
|
896
|
+
self._merge_queries(other, self, join_table, join_dict, 'OR')
|
|
897
|
+
else:
|
|
898
|
+
from copy import copy
|
|
899
|
+
if not self.__query_string__:
|
|
900
|
+
return copy(self)
|
|
901
|
+
if not other.__query_string__:
|
|
902
|
+
return copy(other)
|
|
903
|
+
return self._merge_queries(other, copy(self), join_table,
|
|
904
|
+
join_dict, 'OR')
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
class SQLDatabase(SageObject):
|
|
908
|
+
def __init__(self, filename=None, read_only=None, skeleton=None):
|
|
909
|
+
r"""
|
|
910
|
+
A SQL Database object corresponding to a database file.
|
|
911
|
+
|
|
912
|
+
INPUT:
|
|
913
|
+
|
|
914
|
+
- ``filename`` -- string
|
|
915
|
+
- ``skeleton`` -- a triple-indexed dictionary::
|
|
916
|
+
|
|
917
|
+
| - outer key -- table name
|
|
918
|
+
| - inner key -- column name
|
|
919
|
+
| - inner inner key -- one of the following:
|
|
920
|
+
| - ``primary_key`` -- boolean, whether column has been set
|
|
921
|
+
as primary key
|
|
922
|
+
| - ``index`` -- boolean, whether column has been set as
|
|
923
|
+
index
|
|
924
|
+
| - ``unique`` -- boolean, whether column has been set as
|
|
925
|
+
unique
|
|
926
|
+
| - ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
|
|
927
|
+
``'INTEGER'``, ``'REAL'``, or other user defined type
|
|
928
|
+
|
|
929
|
+
TUTORIAL:
|
|
930
|
+
|
|
931
|
+
The ``SQLDatabase`` class is for interactively building databases
|
|
932
|
+
intended for queries. This may sound redundant, but it is important. If
|
|
933
|
+
you want a database intended for quick lookup of entries in very large
|
|
934
|
+
tables, much like a hash table (such as a Python dictionary), a
|
|
935
|
+
``SQLDatabase`` may not be what you are looking for. The strength of
|
|
936
|
+
``SQLDatabases`` is in queries, searches through the database with
|
|
937
|
+
complicated criteria.
|
|
938
|
+
|
|
939
|
+
For example, we create a new database for storing isomorphism classes
|
|
940
|
+
of simple graphs::
|
|
941
|
+
|
|
942
|
+
sage: D = SQLDatabase()
|
|
943
|
+
|
|
944
|
+
In order to generate representatives for the classes, we will import a
|
|
945
|
+
function which generates all labeled graphs (noting that this is not
|
|
946
|
+
the optimal way)::
|
|
947
|
+
|
|
948
|
+
sage: # needs sage.graphs
|
|
949
|
+
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import all_labeled_graphs
|
|
950
|
+
|
|
951
|
+
We will need a table in the database in which to store the graphs, and
|
|
952
|
+
we specify its structure with a Python dictionary, each of whose keys
|
|
953
|
+
is the name of a column::
|
|
954
|
+
|
|
955
|
+
sage: from collections import OrderedDict
|
|
956
|
+
sage: table_skeleton = OrderedDict([
|
|
957
|
+
....: ('graph6',{'sql':'TEXT', 'index':True, 'primary_key':True}),
|
|
958
|
+
....: ('vertices', {'sql':'INTEGER'}),
|
|
959
|
+
....: ('edges', {'sql':'INTEGER'})
|
|
960
|
+
....: ])
|
|
961
|
+
|
|
962
|
+
Then we create the table::
|
|
963
|
+
|
|
964
|
+
sage: D.create_table('simon', table_skeleton)
|
|
965
|
+
sage: D.show('simon')
|
|
966
|
+
graph6 vertices edges
|
|
967
|
+
------------------------------------------------------------
|
|
968
|
+
|
|
969
|
+
Now that we have the table, we will begin to populate the table with
|
|
970
|
+
rows. First, add the graph on zero vertices.::
|
|
971
|
+
|
|
972
|
+
sage: # needs sage.graphs
|
|
973
|
+
sage: G = Graph()
|
|
974
|
+
sage: D.add_row('simon',(G.graph6_string(), 0, 0))
|
|
975
|
+
sage: D.show('simon')
|
|
976
|
+
graph6 vertices edges
|
|
977
|
+
------------------------------------------------------------
|
|
978
|
+
? 0 0
|
|
979
|
+
|
|
980
|
+
Next, add the graph on one vertex.::
|
|
981
|
+
|
|
982
|
+
sage: # needs sage.graphs
|
|
983
|
+
sage: G.add_vertex()
|
|
984
|
+
0
|
|
985
|
+
sage: D.add_row('simon',(G.graph6_string(), 1, 0))
|
|
986
|
+
sage: D.show('simon')
|
|
987
|
+
graph6 vertices edges
|
|
988
|
+
------------------------------------------------------------
|
|
989
|
+
? 0 0
|
|
990
|
+
@ 1 0
|
|
991
|
+
|
|
992
|
+
Say we want a database of graphs on four or less vertices::
|
|
993
|
+
|
|
994
|
+
sage: # needs sage.graphs
|
|
995
|
+
sage: labels = {}
|
|
996
|
+
sage: for i in range(2, 5):
|
|
997
|
+
....: labels[i] = []
|
|
998
|
+
....: for g in all_labeled_graphs(i):
|
|
999
|
+
....: g = g.canonical_label(algorithm='sage')
|
|
1000
|
+
....: if g not in labels[i]:
|
|
1001
|
+
....: labels[i].append(g)
|
|
1002
|
+
....: D.add_row('simon', (g.graph6_string(), g.order(), g.size()))
|
|
1003
|
+
sage: D.show('simon')
|
|
1004
|
+
graph6 vertices edges
|
|
1005
|
+
------------------------------------------------------------
|
|
1006
|
+
? 0 0
|
|
1007
|
+
@ 1 0
|
|
1008
|
+
A? 2 0
|
|
1009
|
+
A_ 2 1
|
|
1010
|
+
B? 3 0
|
|
1011
|
+
BG 3 1
|
|
1012
|
+
BW 3 2
|
|
1013
|
+
Bw 3 3
|
|
1014
|
+
C? 4 0
|
|
1015
|
+
C@ 4 1
|
|
1016
|
+
CB 4 2
|
|
1017
|
+
CF 4 3
|
|
1018
|
+
CJ 4 3
|
|
1019
|
+
CK 4 2
|
|
1020
|
+
CL 4 3
|
|
1021
|
+
CN 4 4
|
|
1022
|
+
C] 4 4
|
|
1023
|
+
C^ 4 5
|
|
1024
|
+
C~ 4 6
|
|
1025
|
+
|
|
1026
|
+
We can then query the database -- let's ask for all the graphs on four
|
|
1027
|
+
vertices with three edges. We do so by creating two queries and asking
|
|
1028
|
+
for rows that satisfy them both::
|
|
1029
|
+
|
|
1030
|
+
sage: # needs sage.graphs
|
|
1031
|
+
sage: Q = SQLQuery(D, {'table_name':'simon', 'display_cols':['graph6'], 'expression':['vertices','=',4]})
|
|
1032
|
+
sage: Q2 = SQLQuery(D, {'table_name':'simon', 'display_cols':['graph6'], 'expression':['edges','=',3]})
|
|
1033
|
+
sage: Q = Q.intersect(Q2)
|
|
1034
|
+
sage: len(Q.query_results())
|
|
1035
|
+
3
|
|
1036
|
+
sage: Q.query_results() # random
|
|
1037
|
+
[('CF', 'CF'), ('CJ', 'CJ'), ('CL', 'CL')]
|
|
1038
|
+
|
|
1039
|
+
NOTE: The values of ``display_cols`` are always concatenated in
|
|
1040
|
+
intersections and unions.
|
|
1041
|
+
|
|
1042
|
+
Of course, we can save the database to file. Here we use a
|
|
1043
|
+
temporary directory that we clean up afterwards::
|
|
1044
|
+
|
|
1045
|
+
sage: import tempfile
|
|
1046
|
+
sage: d = tempfile.TemporaryDirectory()
|
|
1047
|
+
sage: dbpath = os.path.join(d.name, 'simon.db')
|
|
1048
|
+
sage: D.save(dbpath)
|
|
1049
|
+
|
|
1050
|
+
Now the database's hard link is to this file, and not the temporary db
|
|
1051
|
+
file. For example, let's say we open the same file with another class
|
|
1052
|
+
instance. We can load the file as an immutable database::
|
|
1053
|
+
|
|
1054
|
+
sage: # needs sage.graphs
|
|
1055
|
+
sage: E = SQLDatabase(dbpath)
|
|
1056
|
+
sage: E.show('simon')
|
|
1057
|
+
graph6 vertices edges
|
|
1058
|
+
------------------------------------------------------------
|
|
1059
|
+
? 0 0
|
|
1060
|
+
@ 1 0
|
|
1061
|
+
A? 2 0
|
|
1062
|
+
A_ 2 1
|
|
1063
|
+
B? 3 0
|
|
1064
|
+
BG 3 1
|
|
1065
|
+
BW 3 2
|
|
1066
|
+
Bw 3 3
|
|
1067
|
+
C? 4 0
|
|
1068
|
+
C@ 4 1
|
|
1069
|
+
CB 4 2
|
|
1070
|
+
CF 4 3
|
|
1071
|
+
CJ 4 3
|
|
1072
|
+
CK 4 2
|
|
1073
|
+
CL 4 3
|
|
1074
|
+
CN 4 4
|
|
1075
|
+
C] 4 4
|
|
1076
|
+
C^ 4 5
|
|
1077
|
+
C~ 4 6
|
|
1078
|
+
sage: E.drop_table('simon')
|
|
1079
|
+
Traceback (most recent call last):
|
|
1080
|
+
...
|
|
1081
|
+
RuntimeError: Cannot drop tables from a read only database.
|
|
1082
|
+
|
|
1083
|
+
Call ``cleanup()`` on the temporary directory to, well, clean it up::
|
|
1084
|
+
|
|
1085
|
+
sage: d.cleanup()
|
|
1086
|
+
"""
|
|
1087
|
+
if filename is None:
|
|
1088
|
+
if read_only is None:
|
|
1089
|
+
read_only = False
|
|
1090
|
+
from sage.misc.temporary_file import tmp_filename
|
|
1091
|
+
filename = tmp_filename() + '.db'
|
|
1092
|
+
elif (filename[-3:] != '.db'):
|
|
1093
|
+
raise ValueError('Please enter a valid database path (file name '
|
|
1094
|
+
+ '%s does not end in .db).' % filename)
|
|
1095
|
+
if read_only is None:
|
|
1096
|
+
read_only = True
|
|
1097
|
+
|
|
1098
|
+
self.__read_only__ = read_only
|
|
1099
|
+
self.ignore_warnings = False
|
|
1100
|
+
self.__dblocation__ = filename
|
|
1101
|
+
self.__connection__ = sqlite.connect(self.__dblocation__,
|
|
1102
|
+
check_same_thread=False)
|
|
1103
|
+
# this is to avoid the multiple thread problem with dsage:
|
|
1104
|
+
# pysqlite does not trust multiple threads for the same connection
|
|
1105
|
+
self.__connection__.create_function("regexp", 2, regexp)
|
|
1106
|
+
|
|
1107
|
+
# construct skeleton (from provided database)
|
|
1108
|
+
self.__skeleton__ = construct_skeleton(self)
|
|
1109
|
+
|
|
1110
|
+
# add bones from new skeleton to database,
|
|
1111
|
+
# without changing existing structure
|
|
1112
|
+
if skeleton is not None and not read_only:
|
|
1113
|
+
for table in skeleton:
|
|
1114
|
+
if table not in self.__skeleton__:
|
|
1115
|
+
self.create_table(table, skeleton[table])
|
|
1116
|
+
else:
|
|
1117
|
+
for column in skeleton[table]:
|
|
1118
|
+
if column not in self.__skeleton__[table]:
|
|
1119
|
+
self.add_column(table, column,
|
|
1120
|
+
skeleton[table][column])
|
|
1121
|
+
else:
|
|
1122
|
+
print('Column attributes were ignored for '
|
|
1123
|
+
'table {}, column {} -- column is '
|
|
1124
|
+
'already in table.'.format(table, column))
|
|
1125
|
+
elif skeleton is not None:
|
|
1126
|
+
raise RuntimeError('Cannot update skeleton of a read only '
|
|
1127
|
+
+ 'database.')
|
|
1128
|
+
|
|
1129
|
+
def __repr__(self):
|
|
1130
|
+
"""
|
|
1131
|
+
Override the print output to display useful info regarding the
|
|
1132
|
+
database.
|
|
1133
|
+
|
|
1134
|
+
EXAMPLES::
|
|
1135
|
+
|
|
1136
|
+
sage: import tempfile
|
|
1137
|
+
sage: with tempfile.TemporaryDirectory() as d:
|
|
1138
|
+
....: dbpath = os.path.join(d, "test.db")
|
|
1139
|
+
....: SD = SQLDatabase(dbpath, False)
|
|
1140
|
+
....: SD.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
|
|
1141
|
+
....: print(SD)
|
|
1142
|
+
table simon:
|
|
1143
|
+
column n: index: True; primary_key: False; sql: INTEGER;
|
|
1144
|
+
unique: False;
|
|
1145
|
+
"""
|
|
1146
|
+
s = ''
|
|
1147
|
+
for table in self.__skeleton__:
|
|
1148
|
+
s += 'table ' + table + ':\n'
|
|
1149
|
+
for column in self.__skeleton__[table]:
|
|
1150
|
+
s += ' column ' + column + ': '
|
|
1151
|
+
for data in sorted(self.__skeleton__[table][column]):
|
|
1152
|
+
s += data + ': ' \
|
|
1153
|
+
+ str(self.__skeleton__[table][column][data]) + '; '
|
|
1154
|
+
s += '\n'
|
|
1155
|
+
return s
|
|
1156
|
+
|
|
1157
|
+
def __copy__(self):
|
|
1158
|
+
"""
|
|
1159
|
+
Return an instance of ``SQLDatabase`` that points to a copy database,
|
|
1160
|
+
and allows modification.
|
|
1161
|
+
|
|
1162
|
+
EXAMPLES::
|
|
1163
|
+
|
|
1164
|
+
sage: from collections import OrderedDict
|
|
1165
|
+
sage: DB = SQLDatabase()
|
|
1166
|
+
sage: DB.create_table('lucy', OrderedDict([
|
|
1167
|
+
....: ('id', {'sql':'INTEGER', 'primary_key':True, 'index':True}),
|
|
1168
|
+
....: ('a1', {'sql':'bool'}),
|
|
1169
|
+
....: ('b2', {'sql':'int', 'primary_key':False})
|
|
1170
|
+
....: ]))
|
|
1171
|
+
sage: DB.add_rows('lucy', [(0,1,1),(1,1,4),(2,0,7),(3,1,384), (4,1,978932)],['id','a1','b2'])
|
|
1172
|
+
sage: d = copy(DB)
|
|
1173
|
+
sage: d == DB
|
|
1174
|
+
False
|
|
1175
|
+
sage: d.show('lucy')
|
|
1176
|
+
id a1 b2
|
|
1177
|
+
------------------------------------------------------------
|
|
1178
|
+
0 1 1
|
|
1179
|
+
1 1 4
|
|
1180
|
+
2 0 7
|
|
1181
|
+
3 1 384
|
|
1182
|
+
4 1 978932
|
|
1183
|
+
sage: DB.show('lucy')
|
|
1184
|
+
id a1 b2
|
|
1185
|
+
------------------------------------------------------------
|
|
1186
|
+
0 1 1
|
|
1187
|
+
1 1 4
|
|
1188
|
+
2 0 7
|
|
1189
|
+
3 1 384
|
|
1190
|
+
4 1 978932
|
|
1191
|
+
"""
|
|
1192
|
+
# copy .db file
|
|
1193
|
+
from sage.misc.temporary_file import tmp_filename
|
|
1194
|
+
new_loc = tmp_filename() + '.db'
|
|
1195
|
+
if not self.__read_only__:
|
|
1196
|
+
self.commit()
|
|
1197
|
+
os.system('cp ' + self.__dblocation__ + ' ' + new_loc)
|
|
1198
|
+
D = SQLDatabase(filename=new_loc, read_only=False)
|
|
1199
|
+
return D
|
|
1200
|
+
|
|
1201
|
+
def save(self, filename):
|
|
1202
|
+
"""
|
|
1203
|
+
Save the database to the specified location.
|
|
1204
|
+
|
|
1205
|
+
EXAMPLES::
|
|
1206
|
+
|
|
1207
|
+
sage: MonicPolys = SQLDatabase()
|
|
1208
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
|
|
1209
|
+
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
|
|
1210
|
+
sage: import tempfile
|
|
1211
|
+
sage: with tempfile.TemporaryDirectory() as d:
|
|
1212
|
+
....: dbpath = os.path.join(d, "sage.db")
|
|
1213
|
+
....: MonicPolys.save(dbpath)
|
|
1214
|
+
....: N = SQLDatabase(dbpath)
|
|
1215
|
+
....: N.show('simon')
|
|
1216
|
+
n
|
|
1217
|
+
--------------------
|
|
1218
|
+
0
|
|
1219
|
+
1
|
|
1220
|
+
2
|
|
1221
|
+
3
|
|
1222
|
+
4
|
|
1223
|
+
5
|
|
1224
|
+
6
|
|
1225
|
+
7
|
|
1226
|
+
8
|
|
1227
|
+
9
|
|
1228
|
+
10
|
|
1229
|
+
11
|
|
1230
|
+
12
|
|
1231
|
+
13
|
|
1232
|
+
14
|
|
1233
|
+
15
|
|
1234
|
+
16
|
|
1235
|
+
17
|
|
1236
|
+
18
|
|
1237
|
+
19
|
|
1238
|
+
"""
|
|
1239
|
+
if not self.__read_only__:
|
|
1240
|
+
self.commit()
|
|
1241
|
+
os.system('cp ' + self.__dblocation__ + ' ' + filename)
|
|
1242
|
+
|
|
1243
|
+
def get_skeleton(self, check=False):
|
|
1244
|
+
"""
|
|
1245
|
+
Return a dictionary representing the hierarchical structure of the
|
|
1246
|
+
database, in the following format::
|
|
1247
|
+
|
|
1248
|
+
| - skeleton -- a triple-indexed dictionary
|
|
1249
|
+
| - outer key -- table name
|
|
1250
|
+
| - inner key -- column name
|
|
1251
|
+
| - inner inner key -- one of the following:
|
|
1252
|
+
| - ``primary_key`` -- boolean, whether column has been set as
|
|
1253
|
+
primary key
|
|
1254
|
+
| - ``index`` -- boolean, whether column has been set as index
|
|
1255
|
+
| - ``unique`` -- boolean, whether column has been set as unique
|
|
1256
|
+
| - ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``, ``'INTEGER'``,
|
|
1257
|
+
``'REAL'``, or other user defined type
|
|
1258
|
+
|
|
1259
|
+
For example::
|
|
1260
|
+
|
|
1261
|
+
{'table1':{'col1':{'primary_key':False, 'index':True, 'unique': False,'sql':'REAL'}}}
|
|
1262
|
+
|
|
1263
|
+
INPUT:
|
|
1264
|
+
|
|
1265
|
+
- ``check`` -- if ``True``, checks to make sure the database's actual
|
|
1266
|
+
structure matches the skeleton on record
|
|
1267
|
+
|
|
1268
|
+
EXAMPLES::
|
|
1269
|
+
|
|
1270
|
+
sage: # needs sage.graphs graph_database
|
|
1271
|
+
sage: GDB = GraphDatabase()
|
|
1272
|
+
sage: GDB.get_skeleton() # slightly random output
|
|
1273
|
+
{'aut_grp': {'aut_grp_size': {'index': True,
|
|
1274
|
+
'unique': False,
|
|
1275
|
+
'primary_key': False,
|
|
1276
|
+
'sql': 'INTEGER'},
|
|
1277
|
+
...
|
|
1278
|
+
'num_vertices': {'index': True,
|
|
1279
|
+
'unique': False,
|
|
1280
|
+
'primary_key': False,
|
|
1281
|
+
'sql': 'INTEGER'}}}
|
|
1282
|
+
"""
|
|
1283
|
+
if check:
|
|
1284
|
+
d = construct_skeleton(self)
|
|
1285
|
+
if d == self.__skeleton__:
|
|
1286
|
+
return d
|
|
1287
|
+
raise RuntimeError("skeleton structure is out of whack")
|
|
1288
|
+
return self.__skeleton__
|
|
1289
|
+
|
|
1290
|
+
def query(self, *args, **kwds):
|
|
1291
|
+
"""
|
|
1292
|
+
Create a ``SQLQuery`` on this database.
|
|
1293
|
+
|
|
1294
|
+
For full class details,
|
|
1295
|
+
type ``SQLQuery?`` and press :kbd:`Shift` + :kbd:`Enter`.
|
|
1296
|
+
|
|
1297
|
+
EXAMPLES::
|
|
1298
|
+
|
|
1299
|
+
sage: D = SQLDatabase()
|
|
1300
|
+
sage: D.create_table('simon', {'wolf':{'sql':'BOOLEAN'}, 'tag':{'sql':'INTEGER'}})
|
|
1301
|
+
sage: q = D.query({'table_name':'simon', 'display_cols':['tag'], 'expression':['wolf','=',1]})
|
|
1302
|
+
sage: q.get_query_string()
|
|
1303
|
+
'SELECT simon.tag FROM simon WHERE simon.wolf = ?'
|
|
1304
|
+
sage: q.__param_tuple__
|
|
1305
|
+
('1',)
|
|
1306
|
+
sage: q = D.query(query_string='SELECT tag FROM simon WHERE wolf=?',param_tuple=(1,))
|
|
1307
|
+
sage: q.get_query_string()
|
|
1308
|
+
'SELECT tag FROM simon WHERE wolf=?'
|
|
1309
|
+
sage: q.__param_tuple__
|
|
1310
|
+
('1',)
|
|
1311
|
+
"""
|
|
1312
|
+
return SQLQuery(self, *args, **kwds)
|
|
1313
|
+
|
|
1314
|
+
__call__ = query
|
|
1315
|
+
|
|
1316
|
+
def show(self, table_name, **kwds):
|
|
1317
|
+
"""
|
|
1318
|
+
Show an entire table from the database.
|
|
1319
|
+
|
|
1320
|
+
EXAMPLES::
|
|
1321
|
+
|
|
1322
|
+
sage: DB = SQLDatabase()
|
|
1323
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
1324
|
+
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
|
|
1325
|
+
sage: DB.show('simon')
|
|
1326
|
+
a1 b2
|
|
1327
|
+
----------------------------------------
|
|
1328
|
+
0 0
|
|
1329
|
+
1 1
|
|
1330
|
+
1 2
|
|
1331
|
+
"""
|
|
1332
|
+
try:
|
|
1333
|
+
cur = self.__connection__.cursor()
|
|
1334
|
+
cur.execute('SELECT * FROM ' + table_name)
|
|
1335
|
+
except Exception:
|
|
1336
|
+
raise RuntimeError('Failure to fetch data.')
|
|
1337
|
+
print(_create_print_table(cur, [des[0] for des in cur.description],
|
|
1338
|
+
**kwds))
|
|
1339
|
+
|
|
1340
|
+
def get_cursor(self, ignore_warning=None):
|
|
1341
|
+
"""
|
|
1342
|
+
Return a pysqlite cursor for the database connection.
|
|
1343
|
+
|
|
1344
|
+
A cursor is an input from which you can execute sqlite commands on the
|
|
1345
|
+
database.
|
|
1346
|
+
|
|
1347
|
+
Recommended for more advanced users only.
|
|
1348
|
+
|
|
1349
|
+
EXAMPLES::
|
|
1350
|
+
|
|
1351
|
+
sage: DB = SQLDatabase()
|
|
1352
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
1353
|
+
sage: DB.add_row('simon',(0,1))
|
|
1354
|
+
sage: DB.add_rows('simon',[(0,0),(1,1),(1,2)])
|
|
1355
|
+
sage: DB.add_rows('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
|
|
1356
|
+
sage: cur = DB.get_cursor()
|
|
1357
|
+
sage: (cur.execute('select * from simon')).fetchall()
|
|
1358
|
+
[(0, 1), (0, 0), (1, 1), (1, 2), (0, 0), (0, 4), (1, 5)]
|
|
1359
|
+
"""
|
|
1360
|
+
if self.__read_only__:
|
|
1361
|
+
if ignore_warning is None:
|
|
1362
|
+
ignore_warning = self.ignore_warnings
|
|
1363
|
+
if not ignore_warning:
|
|
1364
|
+
import warnings
|
|
1365
|
+
warnings.warn('Database is read only, using the cursor can '
|
|
1366
|
+
+ 'alter the stored data. Set self.ignore_warnings to '
|
|
1367
|
+
+ 'True in order to mute future warnings.', RuntimeWarning)
|
|
1368
|
+
return self.__connection__.cursor()
|
|
1369
|
+
|
|
1370
|
+
def get_connection(self, ignore_warning=None):
|
|
1371
|
+
"""
|
|
1372
|
+
Return a pysqlite connection to the database.
|
|
1373
|
+
|
|
1374
|
+
You most likely want ``get_cursor()`` instead, which is used for
|
|
1375
|
+
executing sqlite commands on the database.
|
|
1376
|
+
|
|
1377
|
+
Recommended for more advanced users only.
|
|
1378
|
+
|
|
1379
|
+
EXAMPLES::
|
|
1380
|
+
|
|
1381
|
+
sage: D = SQLDatabase(read_only=True)
|
|
1382
|
+
sage: con = D.get_connection()
|
|
1383
|
+
doctest:...: RuntimeWarning: Database is read only, using the connection can alter the stored data. Set self.ignore_warnings to True in order to mute future warnings.
|
|
1384
|
+
sage: con = D.get_connection(True)
|
|
1385
|
+
sage: D.ignore_warnings = True
|
|
1386
|
+
sage: con = D.get_connection()
|
|
1387
|
+
sage: t = con.execute('CREATE TABLE simon(n INTEGER, n2 INTEGER)')
|
|
1388
|
+
sage: for n in range(10):
|
|
1389
|
+
....: t = con.execute('INSERT INTO simon VALUES(%d,%d)'%(n,n^2))
|
|
1390
|
+
sage: D.show('simon')
|
|
1391
|
+
n n2
|
|
1392
|
+
----------------------------------------
|
|
1393
|
+
0 0
|
|
1394
|
+
1 1
|
|
1395
|
+
2 4
|
|
1396
|
+
3 9
|
|
1397
|
+
4 16
|
|
1398
|
+
5 25
|
|
1399
|
+
6 36
|
|
1400
|
+
7 49
|
|
1401
|
+
8 64
|
|
1402
|
+
9 81
|
|
1403
|
+
"""
|
|
1404
|
+
if self.__read_only__:
|
|
1405
|
+
if ignore_warning is None:
|
|
1406
|
+
ignore_warning = self.ignore_warnings
|
|
1407
|
+
if not ignore_warning:
|
|
1408
|
+
import warnings
|
|
1409
|
+
warnings.warn('Database is read only, using the connection '
|
|
1410
|
+
'can alter the stored data. Set self.ignore_warnings '
|
|
1411
|
+
'to True in order to mute future warnings.',
|
|
1412
|
+
RuntimeWarning)
|
|
1413
|
+
return self.__connection__
|
|
1414
|
+
|
|
1415
|
+
def create_table(self, table_name, table_skeleton):
|
|
1416
|
+
"""
|
|
1417
|
+
Create a new table in the database.
|
|
1418
|
+
|
|
1419
|
+
To create a table, a column structure must be specified. The form for
|
|
1420
|
+
this is a Python dict, for example::
|
|
1421
|
+
|
|
1422
|
+
{'col1': {'sql':'INTEGER', 'index':False, 'unique':True, 'primary_key':False}, ...}
|
|
1423
|
+
|
|
1424
|
+
INPUT:
|
|
1425
|
+
|
|
1426
|
+
- ``table_name`` -- string
|
|
1427
|
+
- ``table_skeleton`` -- a double-indexed dictionary
|
|
1428
|
+
|
|
1429
|
+
- outer key -- column name
|
|
1430
|
+
|
|
1431
|
+
- inner key -- one of the following:
|
|
1432
|
+
|
|
1433
|
+
- ``primary_key`` -- boolean, whether column has been set
|
|
1434
|
+
asprimary key
|
|
1435
|
+
- ``index`` -- boolean, whether column has been set as
|
|
1436
|
+
index
|
|
1437
|
+
- ``unique`` -- boolean, whether column has been set as
|
|
1438
|
+
unique
|
|
1439
|
+
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
|
|
1440
|
+
``'INTEGER'``, ``'REAL'``, or other user defined type
|
|
1441
|
+
|
|
1442
|
+
NOTE:
|
|
1443
|
+
|
|
1444
|
+
Some SQL features, such as automatically incrementing primary key,
|
|
1445
|
+
require the full word ``'INTEGER'``, not just ``'INT'``.
|
|
1446
|
+
|
|
1447
|
+
EXAMPLES::
|
|
1448
|
+
|
|
1449
|
+
sage: from collections import OrderedDict
|
|
1450
|
+
sage: D = SQLDatabase()
|
|
1451
|
+
sage: table_skeleton = OrderedDict([
|
|
1452
|
+
....: ('graph6', {'sql':'TEXT', 'index':True, 'primary_key':True}),
|
|
1453
|
+
....: ('vertices', {'sql':'INTEGER'}),
|
|
1454
|
+
....: ('edges', {'sql':'INTEGER'})
|
|
1455
|
+
....: ])
|
|
1456
|
+
sage: D.create_table('simon', table_skeleton)
|
|
1457
|
+
sage: D.show('simon')
|
|
1458
|
+
graph6 vertices edges
|
|
1459
|
+
------------------------------------------------------------
|
|
1460
|
+
"""
|
|
1461
|
+
if self.__read_only__:
|
|
1462
|
+
raise RuntimeError('Cannot add table to a read only database.')
|
|
1463
|
+
if table_name in self.__skeleton__:
|
|
1464
|
+
raise ValueError('Database already has a table named %s'
|
|
1465
|
+
% table_name)
|
|
1466
|
+
if table_name.find(' ') != -1:
|
|
1467
|
+
raise ValueError('Table names cannot contain spaces.')
|
|
1468
|
+
if table_name.upper() in sqlite_keywords:
|
|
1469
|
+
raise ValueError('Table names cannot be a SQLite keyword.')
|
|
1470
|
+
create_statement = 'CREATE TABLE ' + table_name + '('
|
|
1471
|
+
statement = []
|
|
1472
|
+
index_statement = ''
|
|
1473
|
+
for col in table_skeleton:
|
|
1474
|
+
if col.find('sqlite') != -1:
|
|
1475
|
+
raise ValueError("Column names cannot contain 'sqlite'.")
|
|
1476
|
+
if col.upper() in sqlite_keywords:
|
|
1477
|
+
raise ValueError('Column names cannot be a SQLite keyword.')
|
|
1478
|
+
table_skeleton[col] = verify_column(table_skeleton[col])
|
|
1479
|
+
typ = table_skeleton[col]['sql']
|
|
1480
|
+
if verify_type(typ):
|
|
1481
|
+
if typ.upper() == 'NOTYPE':
|
|
1482
|
+
typ = ''
|
|
1483
|
+
if table_skeleton[col]['primary_key']:
|
|
1484
|
+
statement.append(col + ' ' + typ + ' PRIMARY KEY')
|
|
1485
|
+
elif table_skeleton[col]['unique']:
|
|
1486
|
+
statement.append(col + ' ' + typ + ' UNIQUE')
|
|
1487
|
+
else:
|
|
1488
|
+
statement.append(col + ' ' + typ)
|
|
1489
|
+
if table_skeleton[col]['index']:
|
|
1490
|
+
index_statement += 'CREATE INDEX i_%s_%s' % (table_name,
|
|
1491
|
+
col) + ' ON %s(%s);\n' % (table_name, col)
|
|
1492
|
+
create_statement += ', '.join(statement) + ') '
|
|
1493
|
+
|
|
1494
|
+
self.__connection__.execute(create_statement)
|
|
1495
|
+
if index_statement:
|
|
1496
|
+
self.__connection__.executescript(index_statement)
|
|
1497
|
+
|
|
1498
|
+
self.__skeleton__[table_name] = table_skeleton
|
|
1499
|
+
|
|
1500
|
+
def add_column(self, table_name, col_name, col_dict, default='NULL'):
|
|
1501
|
+
"""
|
|
1502
|
+
Add a column named ``col_name`` to table ``table_name``, whose data
|
|
1503
|
+
types are described by ``col_dict``. The format for this is::
|
|
1504
|
+
|
|
1505
|
+
{'col1':{'primary_key':False, 'unique': True, 'index':True, 'sql':'REAL'}}
|
|
1506
|
+
|
|
1507
|
+
INPUT:
|
|
1508
|
+
|
|
1509
|
+
- ``col_dict`` -- dictionary:
|
|
1510
|
+
|
|
1511
|
+
- key -- column name
|
|
1512
|
+
|
|
1513
|
+
- inner key -- one of the following:
|
|
1514
|
+
|
|
1515
|
+
- ``primary_key`` -- boolean, whether column has been set
|
|
1516
|
+
as primary key
|
|
1517
|
+
- ``index`` -- boolean, whether column has been set as
|
|
1518
|
+
index
|
|
1519
|
+
- ``unique`` -- boolean, whether column has been set as
|
|
1520
|
+
unique
|
|
1521
|
+
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
|
|
1522
|
+
``'INTEGER'``, ``'REAL'``, or other user defined type
|
|
1523
|
+
|
|
1524
|
+
EXAMPLES::
|
|
1525
|
+
|
|
1526
|
+
sage: from collections import OrderedDict
|
|
1527
|
+
sage: MonicPolys = SQLDatabase()
|
|
1528
|
+
sage: MonicPolys.create_table('simon', OrderedDict([('n', {'sql':'INTEGER', 'index':True})]))
|
|
1529
|
+
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
|
|
1530
|
+
sage: MonicPolys.add_column('simon', 'n_squared', {'sql':'INTEGER', 'index':False}, 0)
|
|
1531
|
+
sage: MonicPolys.show('simon')
|
|
1532
|
+
n n_squared
|
|
1533
|
+
----------------------------------------
|
|
1534
|
+
0 0
|
|
1535
|
+
1 0
|
|
1536
|
+
2 0
|
|
1537
|
+
3 0
|
|
1538
|
+
4 0
|
|
1539
|
+
5 0
|
|
1540
|
+
6 0
|
|
1541
|
+
7 0
|
|
1542
|
+
8 0
|
|
1543
|
+
9 0
|
|
1544
|
+
10 0
|
|
1545
|
+
11 0
|
|
1546
|
+
12 0
|
|
1547
|
+
13 0
|
|
1548
|
+
14 0
|
|
1549
|
+
15 0
|
|
1550
|
+
16 0
|
|
1551
|
+
17 0
|
|
1552
|
+
18 0
|
|
1553
|
+
19 0
|
|
1554
|
+
"""
|
|
1555
|
+
if self.__read_only__:
|
|
1556
|
+
raise RuntimeError('Cannot add columns to a read only database.')
|
|
1557
|
+
# Check input:
|
|
1558
|
+
if col_name.find('sqlite') != -1:
|
|
1559
|
+
raise ValueError("Column names cannot contain 'sqlite'.")
|
|
1560
|
+
if col_name.upper() in sqlite_keywords:
|
|
1561
|
+
raise ValueError("Column names cannot be SQLite keywords.")
|
|
1562
|
+
if table_name not in self.__skeleton__:
|
|
1563
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1564
|
+
if col_name in self.__skeleton__[table_name]:
|
|
1565
|
+
raise ValueError("Table %s already has column %s." % (table_name,col_name))
|
|
1566
|
+
|
|
1567
|
+
# Update the skeleton:
|
|
1568
|
+
self.__skeleton__[table_name][col_name] = verify_column(col_dict)
|
|
1569
|
+
|
|
1570
|
+
try:
|
|
1571
|
+
self._rebuild_table(table_name, col_name, default)
|
|
1572
|
+
except sqlite.Error as e:
|
|
1573
|
+
# delete added column from skeleton
|
|
1574
|
+
self.__skeleton__[table_name].pop(col_name)
|
|
1575
|
+
|
|
1576
|
+
print('A sqlite error occurred: ', e.args[0])
|
|
1577
|
+
|
|
1578
|
+
def _rebuild_table(self, table_name, col_name=None, default=''):
|
|
1579
|
+
"""
|
|
1580
|
+
Rebuild the table ``table_name`` adding column ``col_name`` if not
|
|
1581
|
+
``None``. If a new column is added, each rows' value is set to
|
|
1582
|
+
``default``.
|
|
1583
|
+
|
|
1584
|
+
Used in the methods ``add_column``, ``drop_column``, ``make_unique``,
|
|
1585
|
+
``drop_unique``, ``make_primary_key``, and ``drop_primary_key`` to get
|
|
1586
|
+
around the limitations of SQLite's ``ALTER TABLE``. We have set up the
|
|
1587
|
+
creation of a temporary database in this method. Please feel free to
|
|
1588
|
+
improve on this by sending a patch or suggestion.
|
|
1589
|
+
|
|
1590
|
+
EXAMPLES::
|
|
1591
|
+
|
|
1592
|
+
sage: MonicPolys = SQLDatabase()
|
|
1593
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
|
|
1594
|
+
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
|
|
1595
|
+
sage: MonicPolys.show('simon')
|
|
1596
|
+
n
|
|
1597
|
+
--------------------
|
|
1598
|
+
0
|
|
1599
|
+
1
|
|
1600
|
+
2
|
|
1601
|
+
3
|
|
1602
|
+
4
|
|
1603
|
+
5
|
|
1604
|
+
6
|
|
1605
|
+
7
|
|
1606
|
+
8
|
|
1607
|
+
9
|
|
1608
|
+
10
|
|
1609
|
+
11
|
|
1610
|
+
12
|
|
1611
|
+
13
|
|
1612
|
+
14
|
|
1613
|
+
15
|
|
1614
|
+
16
|
|
1615
|
+
17
|
|
1616
|
+
18
|
|
1617
|
+
19
|
|
1618
|
+
sage: MonicPolys._rebuild_table('simon')
|
|
1619
|
+
sage: MonicPolys.show('simon')
|
|
1620
|
+
n
|
|
1621
|
+
--------------------
|
|
1622
|
+
0
|
|
1623
|
+
1
|
|
1624
|
+
2
|
|
1625
|
+
3
|
|
1626
|
+
4
|
|
1627
|
+
5
|
|
1628
|
+
6
|
|
1629
|
+
7
|
|
1630
|
+
8
|
|
1631
|
+
9
|
|
1632
|
+
10
|
|
1633
|
+
11
|
|
1634
|
+
12
|
|
1635
|
+
13
|
|
1636
|
+
14
|
|
1637
|
+
15
|
|
1638
|
+
16
|
|
1639
|
+
17
|
|
1640
|
+
18
|
|
1641
|
+
19
|
|
1642
|
+
"""
|
|
1643
|
+
cols = []
|
|
1644
|
+
cols_attr = []
|
|
1645
|
+
table_skeleton = self.__skeleton__[table_name]
|
|
1646
|
+
# gather column names and attributes for new table
|
|
1647
|
+
for col in table_skeleton:
|
|
1648
|
+
cols.append(col)
|
|
1649
|
+
attr_str = col + ' ' + table_skeleton[col]['sql']
|
|
1650
|
+
if table_skeleton[col]['primary_key']:
|
|
1651
|
+
attr_str += ' PRIMARY KEY'
|
|
1652
|
+
elif table_skeleton[col]['unique']:
|
|
1653
|
+
attr_str += ' UNIQUE'
|
|
1654
|
+
cols_attr.append(attr_str)
|
|
1655
|
+
|
|
1656
|
+
original = list(cols)
|
|
1657
|
+
|
|
1658
|
+
if col_name is not None:
|
|
1659
|
+
original[original.index(col_name)] = str(default)
|
|
1660
|
+
|
|
1661
|
+
original = ', '.join(original)
|
|
1662
|
+
cols = ', '.join(cols)
|
|
1663
|
+
cols_attr = ', '.join(cols_attr)
|
|
1664
|
+
|
|
1665
|
+
# Silly SQLite -- we have to make a temp table to hold info...
|
|
1666
|
+
self.__connection__.executescript("""
|
|
1667
|
+
CREATE TEMPORARY TABLE spam(%s);
|
|
1668
|
+
INSERT INTO spam SELECT %s FROM %s;
|
|
1669
|
+
DROP TABLE %s;
|
|
1670
|
+
CREATE TABLE %s (%s);
|
|
1671
|
+
""" % (cols_attr, original, table_name, table_name, table_name, cols_attr))
|
|
1672
|
+
# Update indices in new table
|
|
1673
|
+
skeleton = self.__skeleton__[table_name]
|
|
1674
|
+
index_statement = ''.join(f'CREATE INDEX i_{table_name}_{col} ON '
|
|
1675
|
+
+ f'{table_name}({col});\n'
|
|
1676
|
+
for col in skeleton
|
|
1677
|
+
if skeleton[col]['index']
|
|
1678
|
+
and not skeleton[col]['primary_key'])
|
|
1679
|
+
if index_statement:
|
|
1680
|
+
self.__connection__.executescript(index_statement)
|
|
1681
|
+
|
|
1682
|
+
# Now we can plop our data into the *new* table:
|
|
1683
|
+
self.__connection__.executescript("""
|
|
1684
|
+
INSERT INTO %s SELECT %s FROM spam;
|
|
1685
|
+
DROP TABLE spam;
|
|
1686
|
+
""" % (table_name, cols))
|
|
1687
|
+
|
|
1688
|
+
self.vacuum()
|
|
1689
|
+
|
|
1690
|
+
def drop_column(self, table_name, col_name):
|
|
1691
|
+
"""
|
|
1692
|
+
Drop the column ``col_name`` from table ``table_name``.
|
|
1693
|
+
|
|
1694
|
+
EXAMPLES::
|
|
1695
|
+
|
|
1696
|
+
sage: MonicPolys = SQLDatabase()
|
|
1697
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
|
|
1698
|
+
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
|
|
1699
|
+
sage: MonicPolys.add_column('simon', 'n_squared', {'sql':'INTEGER'}, 0)
|
|
1700
|
+
sage: MonicPolys.drop_column('simon', 'n_squared')
|
|
1701
|
+
sage: MonicPolys.show('simon')
|
|
1702
|
+
n
|
|
1703
|
+
--------------------
|
|
1704
|
+
0
|
|
1705
|
+
1
|
|
1706
|
+
2
|
|
1707
|
+
3
|
|
1708
|
+
4
|
|
1709
|
+
5
|
|
1710
|
+
6
|
|
1711
|
+
7
|
|
1712
|
+
8
|
|
1713
|
+
9
|
|
1714
|
+
10
|
|
1715
|
+
11
|
|
1716
|
+
12
|
|
1717
|
+
13
|
|
1718
|
+
14
|
|
1719
|
+
15
|
|
1720
|
+
16
|
|
1721
|
+
17
|
|
1722
|
+
18
|
|
1723
|
+
19
|
|
1724
|
+
"""
|
|
1725
|
+
if self.__read_only__:
|
|
1726
|
+
raise RuntimeError('Cannot drop columns in a read only database.')
|
|
1727
|
+
# Check input:
|
|
1728
|
+
if table_name not in self.__skeleton__:
|
|
1729
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1730
|
+
if col_name not in self.__skeleton__[table_name]:
|
|
1731
|
+
raise ValueError("Table %s has no column %s." % (table_name,col_name))
|
|
1732
|
+
|
|
1733
|
+
# Update the skeleton:
|
|
1734
|
+
self.__skeleton__[table_name].pop(col_name)
|
|
1735
|
+
|
|
1736
|
+
self._rebuild_table(table_name)
|
|
1737
|
+
|
|
1738
|
+
def rename_table(self, table_name, new_name):
|
|
1739
|
+
"""
|
|
1740
|
+
Rename the table ``table_name`` to ``new_name``.
|
|
1741
|
+
|
|
1742
|
+
EXAMPLES::
|
|
1743
|
+
|
|
1744
|
+
sage: D = SQLDatabase()
|
|
1745
|
+
sage: D.create_table('simon',{'col1':{'sql':'INTEGER'}})
|
|
1746
|
+
sage: D.show('simon')
|
|
1747
|
+
col1
|
|
1748
|
+
--------------------
|
|
1749
|
+
sage: D.rename_table('simon', 'lucy')
|
|
1750
|
+
sage: D.show('simon')
|
|
1751
|
+
Traceback (most recent call last):
|
|
1752
|
+
...
|
|
1753
|
+
RuntimeError: Failure to fetch data.
|
|
1754
|
+
sage: D.show('lucy')
|
|
1755
|
+
col1
|
|
1756
|
+
--------------------
|
|
1757
|
+
"""
|
|
1758
|
+
if self.__read_only__:
|
|
1759
|
+
raise RuntimeError('Cannot rename tables in a read only database.')
|
|
1760
|
+
# Check input:
|
|
1761
|
+
if table_name not in self.__skeleton__:
|
|
1762
|
+
raise ValueError('Database has no table %s.' % table_name)
|
|
1763
|
+
if new_name in self.__skeleton__:
|
|
1764
|
+
raise ValueError('Database already has table %s.' % new_name)
|
|
1765
|
+
|
|
1766
|
+
self.__connection__.execute('ALTER TABLE %s RENAME TO ' % table_name
|
|
1767
|
+
+ new_name)
|
|
1768
|
+
|
|
1769
|
+
# Update skeleton:
|
|
1770
|
+
self.__skeleton__[new_name] = self.__skeleton__.pop(table_name)
|
|
1771
|
+
|
|
1772
|
+
def drop_table(self, table_name):
|
|
1773
|
+
"""
|
|
1774
|
+
Delete table ``table_name`` from database.
|
|
1775
|
+
|
|
1776
|
+
INPUT:
|
|
1777
|
+
|
|
1778
|
+
- ``table_name`` -- string
|
|
1779
|
+
|
|
1780
|
+
EXAMPLES::
|
|
1781
|
+
|
|
1782
|
+
sage: D = SQLDatabase()
|
|
1783
|
+
sage: D.create_table('simon',{'col1':{'sql':'INTEGER'}})
|
|
1784
|
+
sage: D.show('simon')
|
|
1785
|
+
col1
|
|
1786
|
+
--------------------
|
|
1787
|
+
sage: D.drop_table('simon')
|
|
1788
|
+
sage: D.get_skeleton()
|
|
1789
|
+
{}
|
|
1790
|
+
"""
|
|
1791
|
+
if self.__read_only__:
|
|
1792
|
+
raise RuntimeError('Cannot drop tables from a read only '
|
|
1793
|
+
+ 'database.')
|
|
1794
|
+
if table_name not in self.__skeleton__:
|
|
1795
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1796
|
+
|
|
1797
|
+
self.__connection__.execute('DROP TABLE ' + table_name)
|
|
1798
|
+
|
|
1799
|
+
# Update Skeleton
|
|
1800
|
+
self.__skeleton__.pop(table_name)
|
|
1801
|
+
|
|
1802
|
+
def drop_data_from_table(self, table_name):
|
|
1803
|
+
"""
|
|
1804
|
+
Remove all rows from ``table_name``.
|
|
1805
|
+
|
|
1806
|
+
EXAMPLES::
|
|
1807
|
+
|
|
1808
|
+
sage: D = SQLDatabase()
|
|
1809
|
+
sage: D.create_table('simon',{'col1':{'sql':'INTEGER'}})
|
|
1810
|
+
sage: D.add_row('simon',(9,))
|
|
1811
|
+
sage: D.show('simon')
|
|
1812
|
+
col1
|
|
1813
|
+
--------------------
|
|
1814
|
+
9
|
|
1815
|
+
sage: D.drop_data_from_table('simon')
|
|
1816
|
+
sage: D.show('simon')
|
|
1817
|
+
col1
|
|
1818
|
+
--------------------
|
|
1819
|
+
"""
|
|
1820
|
+
if self.__read_only__:
|
|
1821
|
+
raise RuntimeError('Cannot remove data from a read only database.')
|
|
1822
|
+
if table_name not in self.__skeleton__:
|
|
1823
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1824
|
+
self.__connection__.execute('DELETE FROM ' + table_name)
|
|
1825
|
+
|
|
1826
|
+
def make_index(self, col_name, table_name, unique=False):
|
|
1827
|
+
"""
|
|
1828
|
+
Set the column ``col_name`` in table ``table_name`` to be an index,
|
|
1829
|
+
that is, a column set up to do quick searches on.
|
|
1830
|
+
|
|
1831
|
+
INPUT:
|
|
1832
|
+
|
|
1833
|
+
- ``col_name`` -- string
|
|
1834
|
+
- ``table_name`` -- string
|
|
1835
|
+
- ``unique`` -- requires that there are no multiple entries in the
|
|
1836
|
+
column, makes searching faster
|
|
1837
|
+
|
|
1838
|
+
EXAMPLES::
|
|
1839
|
+
|
|
1840
|
+
sage: MonicPolys = SQLDatabase()
|
|
1841
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
|
|
1842
|
+
sage: MonicPolys.make_index('n2','simon')
|
|
1843
|
+
sage: MonicPolys.get_skeleton()
|
|
1844
|
+
{'simon': {'n': {'index': True,
|
|
1845
|
+
'primary_key': False,
|
|
1846
|
+
'sql': 'INTEGER',
|
|
1847
|
+
'unique': False},
|
|
1848
|
+
'n2': {'index': True,
|
|
1849
|
+
'primary_key': False,
|
|
1850
|
+
'sql': 'INTEGER',
|
|
1851
|
+
'unique': False}}}
|
|
1852
|
+
"""
|
|
1853
|
+
if self.__read_only__:
|
|
1854
|
+
raise RuntimeError('Cannot modify a read only database.')
|
|
1855
|
+
if table_name not in self.__skeleton__:
|
|
1856
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1857
|
+
if col_name not in self.__skeleton__[table_name]:
|
|
1858
|
+
raise ValueError("Table %s has no column %s." % (table_name,col_name))
|
|
1859
|
+
|
|
1860
|
+
if unique:
|
|
1861
|
+
index_string = 'CREATE UNIQUE INDEX ' + col_name + ' ON ' \
|
|
1862
|
+
+ table_name + ' (' + col_name + ')'
|
|
1863
|
+
else:
|
|
1864
|
+
index_string = 'CREATE INDEX ' + col_name + ' ON ' + table_name \
|
|
1865
|
+
+ ' (' + col_name + ')'
|
|
1866
|
+
cur = self.__connection__.cursor()
|
|
1867
|
+
cur.execute(index_string)
|
|
1868
|
+
|
|
1869
|
+
# Update Skeleton
|
|
1870
|
+
self.__skeleton__[table_name][col_name]['index'] = True
|
|
1871
|
+
if unique:
|
|
1872
|
+
self.__skeleton[table_name][col_name]['unique'] = True
|
|
1873
|
+
|
|
1874
|
+
def drop_index(self, table_name, index_name):
|
|
1875
|
+
"""
|
|
1876
|
+
Set the column ``index_name`` in table ``table_name`` to not be an
|
|
1877
|
+
index. See ``make_index()``
|
|
1878
|
+
|
|
1879
|
+
EXAMPLES::
|
|
1880
|
+
|
|
1881
|
+
sage: MonicPolys = SQLDatabase()
|
|
1882
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
|
|
1883
|
+
sage: MonicPolys.drop_index('simon', 'n')
|
|
1884
|
+
sage: MonicPolys.get_skeleton()
|
|
1885
|
+
{'simon': {'n': {'index': False,
|
|
1886
|
+
'primary_key': False,
|
|
1887
|
+
'sql': 'INTEGER',
|
|
1888
|
+
'unique': False},
|
|
1889
|
+
'n2': {'index': False,
|
|
1890
|
+
'primary_key': False,
|
|
1891
|
+
'sql': 'INTEGER',
|
|
1892
|
+
'unique': False}}}
|
|
1893
|
+
"""
|
|
1894
|
+
if self.__read_only__:
|
|
1895
|
+
raise RuntimeError('Cannot modify a read only database.')
|
|
1896
|
+
if table_name not in self.__skeleton__:
|
|
1897
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1898
|
+
if index_name not in self.__skeleton__[table_name]:
|
|
1899
|
+
raise ValueError("Table %s has no column %s." % (table_name,
|
|
1900
|
+
index_name))
|
|
1901
|
+
if not self.__skeleton__[table_name][index_name]['index']:
|
|
1902
|
+
return # silently
|
|
1903
|
+
|
|
1904
|
+
cur = self.__connection__.cursor()
|
|
1905
|
+
cur.execute('DROP INDEX i_' + table_name + '_' + index_name)
|
|
1906
|
+
|
|
1907
|
+
# Update Skeleton
|
|
1908
|
+
self.__skeleton__[table_name][index_name]['index'] = False
|
|
1909
|
+
|
|
1910
|
+
def make_unique(self, table_name, col_name):
|
|
1911
|
+
"""
|
|
1912
|
+
Set the column ``col_name`` in table ``table_name`` to store unique
|
|
1913
|
+
values.
|
|
1914
|
+
|
|
1915
|
+
NOTE:
|
|
1916
|
+
|
|
1917
|
+
This function only adds the requirement for entries in ``col_name`` to
|
|
1918
|
+
be unique, it does not change the values.
|
|
1919
|
+
|
|
1920
|
+
EXAMPLES::
|
|
1921
|
+
|
|
1922
|
+
sage: MonicPolys = SQLDatabase()
|
|
1923
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
|
|
1924
|
+
sage: MonicPolys.make_unique('simon', 'n2')
|
|
1925
|
+
sage: MonicPolys.get_skeleton()
|
|
1926
|
+
{'simon': {'n': {'index': True,
|
|
1927
|
+
'primary_key': False,
|
|
1928
|
+
'sql': 'INTEGER',
|
|
1929
|
+
'unique': False},
|
|
1930
|
+
'n2': {'index': False,
|
|
1931
|
+
'primary_key': False,
|
|
1932
|
+
'sql': 'INTEGER',
|
|
1933
|
+
'unique': True}}}
|
|
1934
|
+
"""
|
|
1935
|
+
if self.__read_only__:
|
|
1936
|
+
raise RuntimeError('Cannot modify a read only database')
|
|
1937
|
+
if table_name not in self.__skeleton__:
|
|
1938
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1939
|
+
if col_name not in self.__skeleton__[table_name]:
|
|
1940
|
+
raise ValueError("Table %s has no column %s." % (table_name, col_name))
|
|
1941
|
+
|
|
1942
|
+
# Update the skeleton:
|
|
1943
|
+
self.__skeleton__[table_name][col_name]['unique'] = True
|
|
1944
|
+
|
|
1945
|
+
self._rebuild_table(table_name)
|
|
1946
|
+
|
|
1947
|
+
def drop_unique(self, table_name, col_name):
|
|
1948
|
+
"""
|
|
1949
|
+
Set the column ``col_name`` in table ``table_name`` not store unique
|
|
1950
|
+
values.
|
|
1951
|
+
|
|
1952
|
+
NOTE:
|
|
1953
|
+
|
|
1954
|
+
This function only removes the requirement for entries in ``col_name``
|
|
1955
|
+
to be unique, it does not delete it.
|
|
1956
|
+
|
|
1957
|
+
EXAMPLES::
|
|
1958
|
+
|
|
1959
|
+
sage: MonicPolys = SQLDatabase()
|
|
1960
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
|
|
1961
|
+
sage: MonicPolys.make_unique('simon', 'n2')
|
|
1962
|
+
sage: MonicPolys.drop_unique('simon', 'n2')
|
|
1963
|
+
sage: MonicPolys.get_skeleton()
|
|
1964
|
+
{'simon': {'n': {'index': True,
|
|
1965
|
+
'primary_key': False,
|
|
1966
|
+
'sql': 'INTEGER',
|
|
1967
|
+
'unique': False},
|
|
1968
|
+
'n2': {'index': False,
|
|
1969
|
+
'primary_key': False,
|
|
1970
|
+
'sql': 'INTEGER',
|
|
1971
|
+
'unique': False}}}
|
|
1972
|
+
"""
|
|
1973
|
+
if self.__read_only__:
|
|
1974
|
+
raise RuntimeError('Cannot modify a read only database.')
|
|
1975
|
+
if table_name not in self.__skeleton__:
|
|
1976
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
1977
|
+
if col_name not in self.__skeleton__[table_name]:
|
|
1978
|
+
raise ValueError("Table %s has no column %s." % (table_name, col_name))
|
|
1979
|
+
if self.__skeleton__[table_name][col_name]['primary_key']:
|
|
1980
|
+
raise ValueError("Primary keys must be unique.")
|
|
1981
|
+
|
|
1982
|
+
# Update the skeleton:
|
|
1983
|
+
self.__skeleton__[table_name][col_name]['unique'] = False
|
|
1984
|
+
|
|
1985
|
+
self._rebuild_table(table_name)
|
|
1986
|
+
|
|
1987
|
+
def make_primary_key(self, table_name, col_name):
|
|
1988
|
+
"""
|
|
1989
|
+
Set the column ``col_name`` in table ``table_name`` to be a primary key.
|
|
1990
|
+
|
|
1991
|
+
A primary key is something like an index, but its main purpose is to
|
|
1992
|
+
link different tables together. This allows searches to be executed on
|
|
1993
|
+
multiple tables that represent maybe different data about the same
|
|
1994
|
+
objects.
|
|
1995
|
+
|
|
1996
|
+
NOTE:
|
|
1997
|
+
|
|
1998
|
+
Some SQL features, such as automatically incrementing primary key,
|
|
1999
|
+
require the full word ``'INTEGER'``, not just ``'INT'``.
|
|
2000
|
+
|
|
2001
|
+
EXAMPLES::
|
|
2002
|
+
|
|
2003
|
+
sage: MonicPolys = SQLDatabase()
|
|
2004
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
|
|
2005
|
+
sage: MonicPolys.make_primary_key('simon', 'n2')
|
|
2006
|
+
sage: MonicPolys.get_skeleton()
|
|
2007
|
+
{'simon': {'n': {'index': True,
|
|
2008
|
+
'primary_key': False,
|
|
2009
|
+
'sql': 'INTEGER',
|
|
2010
|
+
'unique': False},
|
|
2011
|
+
'n2': {'index': False,
|
|
2012
|
+
'primary_key': True,
|
|
2013
|
+
'sql': 'INTEGER',
|
|
2014
|
+
'unique': True}}}
|
|
2015
|
+
"""
|
|
2016
|
+
if self.__read_only__:
|
|
2017
|
+
raise RuntimeError('Cannot modify a read only database.')
|
|
2018
|
+
if table_name not in self.__skeleton__:
|
|
2019
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
2020
|
+
if col_name not in self.__skeleton__[table_name]:
|
|
2021
|
+
raise ValueError("Table %s has no column %s." % (table_name, col_name))
|
|
2022
|
+
|
|
2023
|
+
# Update the skeleton:
|
|
2024
|
+
self.__skeleton__[table_name][col_name]['primary_key'] = True
|
|
2025
|
+
self.__skeleton__[table_name][col_name]['unique'] = True
|
|
2026
|
+
|
|
2027
|
+
self._rebuild_table(table_name)
|
|
2028
|
+
|
|
2029
|
+
def drop_primary_key(self, table_name, col_name):
|
|
2030
|
+
"""
|
|
2031
|
+
Set the column ``col_name`` in table ``table_name`` not to be a primary
|
|
2032
|
+
key.
|
|
2033
|
+
|
|
2034
|
+
A primary key is something like an index, but its main purpose is to
|
|
2035
|
+
link different tables together. This allows searches to be executed on
|
|
2036
|
+
multiple tables that represent maybe different data about the same
|
|
2037
|
+
objects.
|
|
2038
|
+
|
|
2039
|
+
NOTE:
|
|
2040
|
+
|
|
2041
|
+
This function only changes the column to be non-primary, it does not
|
|
2042
|
+
delete it.
|
|
2043
|
+
|
|
2044
|
+
EXAMPLES::
|
|
2045
|
+
|
|
2046
|
+
sage: MonicPolys = SQLDatabase()
|
|
2047
|
+
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
|
|
2048
|
+
sage: MonicPolys.make_primary_key('simon', 'n2')
|
|
2049
|
+
sage: MonicPolys.drop_primary_key('simon', 'n2')
|
|
2050
|
+
sage: MonicPolys.get_skeleton()
|
|
2051
|
+
{'simon': {'n': {'index': True,
|
|
2052
|
+
'primary_key': False,
|
|
2053
|
+
'sql': 'INTEGER',
|
|
2054
|
+
'unique': False},
|
|
2055
|
+
'n2': {'index': False,
|
|
2056
|
+
'primary_key': False,
|
|
2057
|
+
'sql': 'INTEGER',
|
|
2058
|
+
'unique': True}}}
|
|
2059
|
+
"""
|
|
2060
|
+
if self.__read_only__:
|
|
2061
|
+
raise RuntimeError('Cannot modify a read only database.')
|
|
2062
|
+
if table_name not in self.__skeleton__:
|
|
2063
|
+
raise ValueError("Database has no table %s." % table_name)
|
|
2064
|
+
if col_name not in self.__skeleton__[table_name]:
|
|
2065
|
+
raise ValueError("Table %s has no column %s." % (table_name,col_name))
|
|
2066
|
+
if not self.__skeleton__[table_name][col_name]['primary_key']:
|
|
2067
|
+
return # silently
|
|
2068
|
+
|
|
2069
|
+
# Update the skeleton:
|
|
2070
|
+
self.__skeleton__[table_name][col_name]['primary_key'] = False
|
|
2071
|
+
|
|
2072
|
+
self._rebuild_table(table_name)
|
|
2073
|
+
|
|
2074
|
+
def add_row(self, table_name, values, entry_order=None):
|
|
2075
|
+
"""
|
|
2076
|
+
Add the row described by ``values`` to the table ``table_name``. Values
|
|
2077
|
+
should be a tuple, of same length and order as columns in given table.
|
|
2078
|
+
|
|
2079
|
+
NOTE:
|
|
2080
|
+
|
|
2081
|
+
If ``values`` is of length one, be sure to specify that it is a tuple of
|
|
2082
|
+
length one, by using a comma, e.g.::
|
|
2083
|
+
|
|
2084
|
+
sage: values = (6,)
|
|
2085
|
+
|
|
2086
|
+
EXAMPLES::
|
|
2087
|
+
|
|
2088
|
+
sage: DB = SQLDatabase()
|
|
2089
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
2090
|
+
sage: DB.add_row('simon',(0,1))
|
|
2091
|
+
sage: cur = DB.get_cursor()
|
|
2092
|
+
sage: (cur.execute('select * from simon')).fetchall()
|
|
2093
|
+
[(0, 1)]
|
|
2094
|
+
"""
|
|
2095
|
+
self.add_rows(table_name, [values], entry_order)
|
|
2096
|
+
|
|
2097
|
+
def delete_rows(self, query):
|
|
2098
|
+
"""
|
|
2099
|
+
Use a ``SQLQuery`` instance to modify (delete rows from) the
|
|
2100
|
+
database.
|
|
2101
|
+
|
|
2102
|
+
``SQLQuery`` must have no join statements. (As of now, you can only
|
|
2103
|
+
delete from one table at a time -- ideas and patches are welcome).
|
|
2104
|
+
|
|
2105
|
+
To remove all data that satisfies a ``SQLQuery``, send the query as an
|
|
2106
|
+
argument to ``delete_rows``. Be careful, test your query first.
|
|
2107
|
+
|
|
2108
|
+
Recommended use: have some kind of row identification primary
|
|
2109
|
+
key column that you use as a parameter in the query. (See example
|
|
2110
|
+
below).
|
|
2111
|
+
|
|
2112
|
+
INPUT:
|
|
2113
|
+
|
|
2114
|
+
- ``query`` -- a ``SQLQuery`` (Delete the rows returned when query is
|
|
2115
|
+
run)
|
|
2116
|
+
|
|
2117
|
+
EXAMPLES::
|
|
2118
|
+
|
|
2119
|
+
sage: from collections import OrderedDict
|
|
2120
|
+
sage: DB = SQLDatabase()
|
|
2121
|
+
sage: DB.create_table('lucy', OrderedDict([
|
|
2122
|
+
....: ('id', {'sql':'INTEGER', 'primary_key':True, 'index':True}),
|
|
2123
|
+
....: ('a1', {'sql':'bool'}),
|
|
2124
|
+
....: ('b2', {'sql':'int'})]))
|
|
2125
|
+
sage: DB.add_rows('lucy', [(0,1,1),(1,1,4),(2,0,7),(3,1,384), (4,1,978932)],['id','a1','b2'])
|
|
2126
|
+
sage: DB.show('lucy')
|
|
2127
|
+
id a1 b2
|
|
2128
|
+
------------------------------------------------------------
|
|
2129
|
+
0 1 1
|
|
2130
|
+
1 1 4
|
|
2131
|
+
2 0 7
|
|
2132
|
+
3 1 384
|
|
2133
|
+
4 1 978932
|
|
2134
|
+
sage: Q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['id','a1','b2'], 'expression':['id','>=',3]})
|
|
2135
|
+
sage: DB.delete_rows(Q)
|
|
2136
|
+
sage: DB.show('lucy')
|
|
2137
|
+
id a1 b2
|
|
2138
|
+
------------------------------------------------------------
|
|
2139
|
+
0 1 1
|
|
2140
|
+
1 1 4
|
|
2141
|
+
2 0 7
|
|
2142
|
+
"""
|
|
2143
|
+
if self.__read_only__:
|
|
2144
|
+
raise RuntimeError('Cannot delete rows from a read only database.')
|
|
2145
|
+
# Check query is associated with this database
|
|
2146
|
+
if not isinstance(query, SQLQuery):
|
|
2147
|
+
raise TypeError('%s is not a valid SQLQuery' % query)
|
|
2148
|
+
if query.__database__ is not self:
|
|
2149
|
+
raise ValueError('%s is not associated to this database.' % query)
|
|
2150
|
+
if (query.__query_string__).find(' JOIN ') != -1:
|
|
2151
|
+
raise ValueError(f'{query} is not a valid query. Can only '
|
|
2152
|
+
'delete from one table at a time.')
|
|
2153
|
+
|
|
2154
|
+
delete_statement = re.sub('SELECT .* FROM', 'DELETE FROM',
|
|
2155
|
+
query.__query_string__)
|
|
2156
|
+
|
|
2157
|
+
try:
|
|
2158
|
+
cur = self.get_cursor()
|
|
2159
|
+
cur.execute(delete_statement, query.__param_tuple__)
|
|
2160
|
+
except Exception:
|
|
2161
|
+
raise RuntimeError('Failure to complete delete. Check your data.')
|
|
2162
|
+
|
|
2163
|
+
def add_rows(self, table_name, rows, entry_order=None):
|
|
2164
|
+
"""
|
|
2165
|
+
INPUT:
|
|
2166
|
+
|
|
2167
|
+
- ``rows`` -- list of tuples that represent one row of data to add
|
|
2168
|
+
(types should match col types in order)
|
|
2169
|
+
- ``entry_order`` -- an ordered list or tuple overrides normal order
|
|
2170
|
+
with user defined order
|
|
2171
|
+
|
|
2172
|
+
EXAMPLES::
|
|
2173
|
+
|
|
2174
|
+
sage: DB = SQLDatabase()
|
|
2175
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
2176
|
+
sage: DB.add_rows('simon',[(0,0),(1,1),(1,2)])
|
|
2177
|
+
sage: DB.add_rows('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
|
|
2178
|
+
sage: cur = DB.get_cursor()
|
|
2179
|
+
sage: (cur.execute('select * from simon')).fetchall()
|
|
2180
|
+
[(0, 0), (1, 1), (1, 2), (0, 0), (0, 4), (1, 5)]
|
|
2181
|
+
"""
|
|
2182
|
+
if self.__read_only__:
|
|
2183
|
+
raise RuntimeError('Cannot add rows to read only database.')
|
|
2184
|
+
quest = '(' + ', '.join('?' for i in rows[0]) + ')'
|
|
2185
|
+
strows = [tuple(str(entry) for entry in row) for row in rows]
|
|
2186
|
+
|
|
2187
|
+
if entry_order is not None:
|
|
2188
|
+
self.__connection__.executemany('INSERT INTO ' + table_name
|
|
2189
|
+
+ str(tuple(entry_order)) + ' VALUES ' + quest, strows)
|
|
2190
|
+
else:
|
|
2191
|
+
self.__connection__.executemany('INSERT INTO ' + table_name
|
|
2192
|
+
+ ' VALUES ' + quest, strows)
|
|
2193
|
+
|
|
2194
|
+
add_data = add_rows
|
|
2195
|
+
|
|
2196
|
+
def vacuum(self):
|
|
2197
|
+
"""
|
|
2198
|
+
Clean the extra hard disk space used up by a database that has
|
|
2199
|
+
recently shrunk.
|
|
2200
|
+
|
|
2201
|
+
EXAMPLES::
|
|
2202
|
+
|
|
2203
|
+
sage: DB = SQLDatabase()
|
|
2204
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
2205
|
+
sage: DB.add_row('simon',(0,1))
|
|
2206
|
+
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
|
|
2207
|
+
sage: DB.add_data('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
|
|
2208
|
+
sage: DB.drop_column('simon','b2')
|
|
2209
|
+
sage: DB.commit()
|
|
2210
|
+
sage: DB.vacuum()
|
|
2211
|
+
"""
|
|
2212
|
+
self.__connection__.execute('VACUUM')
|
|
2213
|
+
|
|
2214
|
+
def commit(self):
|
|
2215
|
+
"""
|
|
2216
|
+
Commit changes to file.
|
|
2217
|
+
|
|
2218
|
+
EXAMPLES::
|
|
2219
|
+
|
|
2220
|
+
sage: DB = SQLDatabase()
|
|
2221
|
+
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
|
|
2222
|
+
sage: DB.add_row('simon',(0,1))
|
|
2223
|
+
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
|
|
2224
|
+
sage: DB.add_data('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
|
|
2225
|
+
sage: DB.drop_column('simon','b2')
|
|
2226
|
+
sage: DB.commit()
|
|
2227
|
+
sage: DB.vacuum()
|
|
2228
|
+
"""
|
|
2229
|
+
if self.__read_only__:
|
|
2230
|
+
raise RuntimeError("Cannot commit read only database.")
|
|
2231
|
+
try:
|
|
2232
|
+
self.__connection__.execute('COMMIT')
|
|
2233
|
+
except sqlite.OperationalError:
|
|
2234
|
+
# Not sure why this throws an exception - but without it,
|
|
2235
|
+
# the changes are not committed so it is necessary.
|
|
2236
|
+
pass
|