packages/sql-faker/src/MySql/Generation/Value/LiteralGenerator.php

1<?php
2
3declare(strict_types=1);
4
5namespace SqlFaker\MySql\Generation\Value;
6
7use Faker\Generator as FakerGenerator;
8use LogicException;
9use SqlFaker\Generation\Value\RandomCharacters;
10
11/**
12 * Generates MySql literal bodies for the bounded lexical API.
13 *
14 * Owns the alphabets and numeric formatting used by this dialect.
15 *
16 * @visibility root
17 */
18final class LiteralGenerator
19{
20    private const ALPHA_UNDERSCORE = 'abcdefghijklmnopqrstuvwxyz_';
21    private const ALNUM_UNDERSCORE = 'abcdefghijklmnopqrstuvwxyz0123456789_';
22    private const MIXED_ALNUM_UNDERSCORE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
23    private const HOSTNAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
24    private const HEX_CHARS = '0123456789abcdef';
25    private const DIGIT_CHARS = '0123456789';
26    private const BINARY_CHARS = '01';
27
28    private FakerGenerator $faker;
29
30    private RandomCharacters $characters;
31
32    /**
33     * @param FakerGenerator $faker Source of every choice the generated strings make
34     */
35    public function __construct(FakerGenerator $faker)
36    {
37        $this->faker = $faker;
38        $this->characters = new RandomCharacters($faker);
39    }
40
41    /**
42     * Generate a raw SQL identifier (starts with letter or underscore, followed by alphanumeric/underscore).
43     */
44    public function rawIdentifier(int $minLength = 1, int $maxLength = 12): string
45    {
46        $length = $this->faker->numberBetween(max($minLength, 1), $maxLength);
47
48        return $this->characters->character(self::ALPHA_UNDERSCORE)
49            . $this->characters->string(self::ALNUM_UNDERSCORE, $length - 1);
50    }
51
52    /**
53     * Generate a random string from the mixed alphanumeric+underscore alphabet.
54     */
55    public function mixedAlnumString(int $minLength = 1, int $maxLength = 24): string
56    {
57        return $this->characters->string(self::MIXED_ALNUM_UNDERSCORE, $this->faker->numberBetween($minLength, $maxLength));
58    }
59
60    /**
61     * Generate a random integer literal string.
62     *
63     * @return non-empty-string
64     */
65    public function integerString(int $min = 1, int $max = 9999999999): string
66    {
67        return (string) $this->faker->numberBetween($min, $max);
68    }
69
70    /**
71     * Generate a random hexadecimal string.
72     */
73    public function hexString(int $minLength = 1, int $maxLength = 16): string
74    {
75        return $this->characters->string(self::HEX_CHARS, $this->faker->numberBetween($minLength, $maxLength));
76    }
77
78    /**
79     * Generate a random binary string (0s and 1s).
80     */
81    public function binaryString(int $minLength = 1, int $maxLength = 32): string
82    {
83        return $this->characters->string(self::BINARY_CHARS, $this->faker->numberBetween($minLength, $maxLength));
84    }
85
86    /**
87     * Generate a random decimal literal string.
88     *
89     * @return non-empty-string
90     */
91    public function decimalString(int $precision = 13, int $scale = 6): string
92    {
93        $intDigits = max($precision - $scale, 1);
94        $maxIntPart = (int) str_repeat('9', $intDigits);
95        $maxFracPart = (int) str_repeat('9', max($scale, 2));
96
97        $intPart = $this->faker->numberBetween(0, $maxIntPart);
98        $fracPart = $this->faker->numberBetween(0, $maxFracPart);
99
100        return $intPart . '.' . str_pad((string) $fracPart, max($scale, 2), '0', STR_PAD_LEFT);
101    }
102
103    /**
104     * Generate a random hostname string.
105     *
106     * @return non-empty-string
107     *
108     * @throws LogicException When the requested bounds leave every part of the hostname empty
109     */
110    public function hostnameString(int $minParts = 1, int $maxParts = 3, int $minPartLength = 1, int $maxPartLength = 12): string
111    {
112        $parts = $this->faker->numberBetween($minParts, $maxParts);
113        $segments = [];
114        for ($p = 0; $p < $parts; $p++) {
115            $segments[] = $this->characters->string(self::HOSTNAME_CHARS, $this->faker->numberBetween($minPartLength, $maxPartLength));
116        }
117
118        $hostname = implode('.', $segments);
119        if ($hostname === '') {
120            throw new LogicException('Hostname generation produced an empty result.');
121        }
122
123        return $hostname;
124    }
125
126    /**
127     * Generate a random unsigned big integer string.
128     *
129     * @return non-empty-string
130     */
131    public function unsignedBigIntString(int $minLength = 1, int $maxLength = 20): string
132    {
133        $buf = $this->characters->string(self::DIGIT_CHARS, $this->faker->numberBetween($minLength, $maxLength));
134        $trimmed = ltrim($buf, '0');
135
136        return $trimmed === '' ? '0' : $trimmed;
137    }
138
139    /**
140     * Generate a random long integer string.
141     *
142     * @return non-empty-string
143     */
144    public function longIntString(int $min = 0, int $max = 2147483647): string
145    {
146        $val = $this->faker->numberBetween($min, $max);
147
148        return "{$val}";
149    }
150
151    /**
152     * Generate a random float literal string with exponent.
153     *
154     * @return non-empty-string
155     */
156    public function floatString(string $mantissa, int $minExponent = -20, int $maxExponent = 20): string
157    {
158        return $mantissa . 'e' . $this->faker->numberBetween($minExponent, $maxExponent);
159    }
160
161
162
163
164
165}
166